Tuesday, December 27, 2022

an ode to LiveJournal

 Man, what happened to the community of LiveJournal?

They just sent me a kind of pathos-laden "hey happy 19th anniversary of starting on LJ!"

For those who don't know, it was a fairly popular (at pre-Twitter, pre-FB, pre-YouTube levels of "popular") site that let people have their own blogs, and you could see and comment on the posts of your friends aggregated on a "feed" page.

I didn't post there, since I had already started my own blog (and had just added a comments board that was the nucleus of its own little community - in fact I had a sidebar microblog for other people to write on the front page...) so my LJ account was for following others and participating in comment threads.

It's notable that many of the final-ish entries of my friends (many in 2010, then more in 2016) mention twitter which is where I assume most of that energy left. Actually, to make a Just-So story: 2010 is when folks who were fonder of the community of folks they know in real life jumped off for Facebook, and 2016 is when folks aspiring to get a wider audience and maybe go viral left for Twitter.

LiveJournal encouraged thoughtful writing in paragraphs; I guess medium now has most that vibe. Maybe substack, but they seem to lean on individual newsletter-y bits. But neither medium nor substack emphasize the "shared feed"/wall/stream that twitter, tumblr, instagram, FB etc have, where posts from a variety of people you find (or The Algorithm hopes you will find) interesting will be on a single scrollable page.

As far as I know LiveJournal was the strongest attempt to encourage longer length writing with entries that were then blended onto feed pages. (Standalone blogs had RSS to collate from sites, but Google embraced and then extinguished the most promising attempts to make that friendly to less technical users. I never got into reading via RSS, frankly, because extracting just the text out of the visual context of its home site made me feel something was lost.)

I think that "are you encouraged (by the UI, or the community vibe) write in paragraphs or sentences" - that's a big part of what separated LJ from Twitter (and also old Usenet (which I used to love) from Reddit, which has never really clicked for me.)

And it's just that short-form mojo Twitter that has, (or a visual, easily digestible image-based approach that Instagram, Tumblr, and even FB) which lends itself to The Algorithm mixing and matching and letting you find new people based on what people you already follow are also digging. Which when I write it out, does sound rather herdish, or redolent of the maddening crowd. Finding new interesting people on LJ was slower, and more organic, generally by following up co-commentators on mutual friends, because going through someone's LJ entries was a longer-attention-span thing.

I've been leaning into tumblr more lately, which (like twitter) I'd mostly been using as an information consumer and not a contributor. Sometimes I wonder if I had started reposting my blog content there years ago like I have been on FB, if I might be have found an even stronger community there (or a set of "mutuals" as they're called). Tumblr has cultural space for both long paragraphs and for quick hit images, and a unique style of additive reblogging that keeps contact with the original post while still getting people to riff.

Monday, December 26, 2022

interactive guide to getting a feel for flexbox

An Interactive Guide to Flexbox by Josh W. Comeau.

I admit, I don't think I quite get it... like I wanted a version of the first example, but where it never went to 2 lines, just one line or three, which was the spec, but I couldn't quite find the magic to make it work...

Sunday, December 25, 2022

how often is Christmas on a Sunday?

 Joined my folks for Church stuff this Christmas day, a Sunday, and the minister mentioned we wouldn't have another Christmas Sunday until 2033!

Curious, I made up this jsitor to see which years had Christmas Sundays, and then what was the pattern.

So day of week for a certain date (I'm sure very similar for all post-Feb 28 days, and then probably roughly similar for days before) repeat every 5, 6, and 11 years, depending on if the leap day is with or against it and then sometimes skipped. 

It's nice to be able to compute this stuff out, though not as interesting as 2013's Thanksgiving on the First Day of Hanukkah - something said not to recur for 70,000 years. 

UPDATE: either jsitor isn't design for codepen-typing sharing (like, optimized for live sharing) or I'm doing something wrong, because my snippet went away. But I reconstructed the logic:

function getDayNameForDate(date){
return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][date.getDay()];
}

let lastYearWithXmasSun = 2022;
for(let year = 2023; year <= 2222; year++) {
const date = new Date(`December 25, ${year} 12:00:00`);
const dayName = getDayNameForDate(date);
if(dayName === 'sun'){
console.log(`${year}: ${year - lastYearWithXmasSun}`);
lastYearWithXmasSun = year;
}
}

In general, that refreshes my biases about depending on fewer external sites for my blogs. In particular I'm really said how many embedded videos on my blog have gone away when I look at it on this date feature... I mean many of the links are broken too but they're not as visible a scar. I bemoaned that process, and the disillusion about how a URL could/should be "forever", in November.

Wednesday, December 14, 2022

viewport 101

 The meta "viewport" setting is so fundamental to the mobile web experience that it's easy to miss, and so therefore weird to correct if you don't know what to look for.

It's the line in the header that tells a device (I'm not even sure if it tends to matter on desktop?) roughly how many pixels the screen should start as.

So here's a more or less normal test page with the viewport set.  (Specifically with the fairly typical formulation <meta name="viewport" content="width=device-width,initial-scale=1">)

If you resize a desktop browser window, all the squares stay about the same size. and the flow layout moves them around. I put a watcher on the resize event so it tells you how wide it thinks the encompassing gray box is. 

Here's the same page without the viewport line. On desktop it is practically identical, but if you go into Chrome's developer tools and toggle the device toolbar, you will see something odd... if you put it in Dimensions: responsive, no matter how wide or narrow you make the window, the offsetWidth is "980" - and the boxes shrink like you were zooming out, they don't rearrange. View the "with viewport set" page  and you'll see the desktop-like behavior is restored.


Notice even though I've set the device width to 480, the box thinks it's 980 across...

What's going on? My guess is in the early days of smartphones that had proper browsers (i.e. the 2007 era iPhone) websites obviously weren't set up for mobile... but a typical laptop window of the day might be around 980 pixels, give or take, and so the initial, unzoomed view started at 980 across, so that the (quite possibly table-based) layout of the sites would make sense. That's why a page without the viewport line seems weirdly zoomed out on a real phone, and why the scaling (of the responsive windows, as if you were using different devices) in chrome is so odd. But if you set the width to viewport width to device-width, you get a more reasonable result (heh, my big iPhone Pro Max starts the device-width at 430; my partner's older iPhone X says 375. But of course that's like px, both are "retina" devices with many more actual pixels than that. Still that's useful... I wasn't quite aware that layouts for phone should be focused on that 400px range...)

Of course you're free to manually specify a starting viewport width, ala  <meta name="viewport" content="width=450;" /> (It's bad form and maybe blocked to try and stop your content from being zoomed; that's a big accessibility no-no)

This came up at work - it took me a while to figure out why we were seeing that second style of zooming in chrome, but it was because we were looking at a code snippet in the browser, and not a whole page (which would presumably set the viewport early on) So even though viewport is fundamental to the mobile web, it makes sense that a lot of UI devs haven't run into it; it's probably set up earlier in the project, like in the "boilerplate" code that launched in, and once you set the width to device-width everything pretty much "just works" like you would expect...


Wednesday, November 30, 2022

determining if angle is inside arc on a circle...

Yesterday (November 29, 2022) was the video game Pong's 50th Birthday! Not the first video game but the first to really take off in the public imagination.

To celebrate, I made an (not very) original game: Rotato Solo Pong

You can mess with the program online. (The version on my site adds a little code to help with mobile, so the finger control doesn't drag the whole screen around....)

I used to make a lot of online games and toys but I haven't been lately... going over some of the cool stuff with my niece made me think I should get back into it a bit? It's a very pleasurable and satisfying hobby, even if I don't know how to publicize a game to get it in front of more people...

This one was trickier than I expected - specifically, figuring out if the ball was hitting the paddle, which I was content with saying "is the angle the ball is at relative to the circle (easily calculable with atan2 function) inside the edges of the paddle, when the ball is on the circle border (easily calculable with the dist() function)" 

At the time of development, it was two paddles - a cooler visual but less fun game mechanic, since you barely had to move to hit the ball...

So circles go from 0 degrees to 2 * PI... and then they start over (at 3 o'clock on a clockface.)  The trouble occurs at that zero = 2 * PI boundary - like when a paddle is perched with one end on either side, it's tough to figure out what angle values to compare vs the angle of the ball.

To work my way through it, I set up a lot of visuals within the game - click here for an interactive recreation to show the problem - an exclamation point shows up when it has detected an overlap of the circle with either paddle:

This shows my first attempt at normalizing angles (like adding 2*PI if the angle was negative, for example) It shows the basic double rotatopong board, with color coded displays  below of the linear values I'm trying to compare to figure out the overlap. You can see that the blue paddle is near that troublesome boundary bit, and so the visual segment of the endpoints (shown underneath) is elongated, and tilted the "wrong direction" 

It was frustrating that this problem was as hard as it was - in fact I reached out to a friend who works at Google but I ended up solving it before he got back to me. One trick was this: for the paddles, I couldn't normalize the angle one at a time: if I had to make an adjustment, it needed to be on both ends of the paddle, so to speak.

That prevented the "weird elongated bar" problem, but there were still misses. My final technique normalizes all angles, but then uses a duplicate of the ball angle - i.e. comparing both the ball angle AND ball angle + 2*PI to see if either is inside the especially normalized paddle end points.

So I think that works? I don't know if there's a more sane way of doing it though. It seems like the problem should be straight forward, but the whole thinking radially is actually pretty tricky...

(I tackled a similar and seeming more difficult problem way back when, figuring out which direction to turn for a heatseeker effect.)



Wednesday, November 23, 2022

javascript translate urls into links, but skipping things that are already links...

A while ago I posted a snippet of linky, and explaining how "changing the current highlighted text into a link" (heuristically figuring out if text to be made clickable or the URL itself) made more sense to me (for use on the backend of my blog) than either markdown-like approaches or dialogs with two input boxes.

But with my work flow, sometimes it was annoying to have to highlight the link in order to linkify it, and I realized a better UX would be "if I click "linky" and there is no text selected, change every https:// starting URL looking thing into a link." 

This stackoverflow was a good start, but assumed any URL wasn't already in a link - like if you ran it twice (or there were links elsewhere) you'd get <a href="<a href=" crap. 

But the regular expression to do it properly was tough. I got some help and ended up with (with a few tester function to start... and pardon the bad pre code ;-) )

function testLinkify(){

   console.log('starting test'); 

   test(`https://foo.com`, `<a href="https://foo.com"></a>`);

   test(`\nhttps://foo.com`,`\n<a href="https://foo.com"></a>`);

   test(`<a href="https://foo.com"></a>`,`<a href="https://foo.com"></a>`);

   test(`<a href="https://foo.com"></a> https://bar.com`,`<a href="https://foo.com"></a> <a href="https://bar.com"></a>`);

     test(`https://foo.com\nhttps://bar.com <a href="https://baz.com">BAZ</a>`,`<a href="https://foo.com"></a>\n<a href="https://bar.com"></a> <a href="https://baz.com">BAZ</a>`);

}

function test(input,expect){

  const testFunction = linkifyBareHttp;

  const output = testFunction(input);

  console.log (output === expect ? 'PASS':'FAIL');

      console.log(` INPUT: ${input}`);

  if(output !== expect) {

    console.log(`EXPECT: ${expect}`);

    console.log(`OUTPUT: ${output}`)

  }

}

 


function linkifyBareHttp(inputText){

    //URLs starting with http://, https://, or ftp://

    const replacePattern1 = /\b(?<!(\'|\"))(((https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]))/gim;

    return inputText.replace(replacePattern1, '<a href="$2"></a>');

}

    

testLinkify();


You can play with that at a codepen

(UPDATE: it was funny playing with my admittedly idiosyncratic UI, starting to use it for real. Turns out I had a workflow that expected to be able to click "linky" before typing anything and just get a place holder "<a href>". Also in theory I might hit linky before any textarea has the focus (it uses the rule of "less touched textarea is the 'current' one") so I special cased a few in the offchance I had started focusing yet... anyway...)     

Wednesday, November 16, 2022

container queries for dummies, the complete idiot's guide to moving beyond media queries

So a lot of sites use CSS media queries to switch between mobile and desktop looks, but sometimes you're more concerned about how much room your widget actually has to work with, rather than what the device itself is.

Enter "Container Queries" - fairly wide support but caniuse points out Firefox is lagging (elsewhere claimed its under way)

I found the MDN pages on container-queries a bit confusing, though (especially how it uses "container" both for a class name even though it's part of the "@container" syntax...(

If you had a media query like 

@media (max-width: 600px) {
  /* … */
}

You might try to replace it with 

@container (max-width: 600px){
/* … */
}

The catch is: some ancestor of the content you are actually concerned about resizing has to act as a "containment context"  - some element with the css property 
 container-type: inline-size;
(inline-size is basically width; block-size could be used for height, or size for both. But I think nine times out of ten you're looking for inline-size there)

Here is a minimalist example codepen I made. Ignore the border and padding I put in for reference...  Ignoring the ugly colors and the border and padding for the wrappers... it demonstrates how the context element can have its own width or be constrained by its own parent, etc.

PS Remember the "_____ for Dummies" books? They started with DOS for Dummies... probably it would have been kinder if the title was "for complete newbies" or "with few assumptions about what you already know", but then the alliteration wouldn't have worked...

nothing is forever. even on the internet.


Thinking of "Twitter 2" and my sudden disinclination to rely on their "embed" feature; I wonder how committed they will be to preserving the viability of "legacy" links and embeds... I mean that's part of what let twitter be at the headwaters of other media, it's paradigm was the quotable soundbite, and that quoting could be done EITHER by copy paste (usually with a link) or embed.

And I see that with all kinds of video I've embedded on my blog, especially video (both youtube videos gone private, and then entire services like Vine gone away)

That kind of linkrot makes me sad. Just reminds me of the old school optimism of how "URLs should be forever", like that somehow the (on paper) possibility of endless, perfect duplication meant the brave new "Information Superhighway" of the Internet would be proof against entropy and organizational decay. 

Well, to quote the old Usenet (remember that?) group alt.folklore.computers: "We're all soldiers in the war against entropy."

never rebuild from scratch

Ha, with Twitter's current instability I guess I should quote this tweet from @GergelyOrosz directly:

Scoop: Elon Musk just sent an email to all staff outlining "Twitter 2.0", writing it will"need to be extremely hardcore". Long hours, high intensity.

People need to click "yes" to confirm being part of this by 5pm ET tomorrow, else they get 3 months severance. More details:

It made me think of Joel Spolsky Things You Should Never Do, Part I where he says never "build it up / rewrite it again from scratch" (Netscape was the precursor to Mozilla/Firefox)

Monday, November 14, 2022

get all properties of the event object...

 Playing around with mouse/touch/pointer events, I was a annoyed that Object.keys(e) only returns "isTrusted" when there's lots of good information there... 

In short there's all kinds of "own" (meaning not inherited from a parent object) and"enumerable" aspects messing things up, but short answer is that this stackoverflow suggests 

function getAllPropertyNames (obj) {
    const proto     = Object.getPrototypeOf(obj);
    const inherited = (proto) ? getAllPropertyNames(proto) : [];
    return [...new Set(Object.getOwnPropertyNames(obj).concat(inherited))];
}

and has some other more specific functions as well.

Thursday, November 10, 2022

sticky headers for sub-parts of page in react

 We had a request to have a sticky header for a sub-part of our page, so that as you scrolled down through a long job listing you would always see the "apply" button. (this is on a complex page with job listing cards on the left and the current job being viewed on the right.)

Frankly we overthought; looked at competitor sites, some of them were playing games with changing the heights of parent containers, or letting the big button header scroll up  but then pushing in a smaller mini version of the header. We thought the solution might need Intersection Observer, or maybe switch to "position:fixed" under conditions or some such, but I think we found something pretty cheap and cheerful that does the job. 

The trick is using "useRef" to get the dom element for the (position: relative) header, getting the parent of the "current" ref, using getBoundingClientRect() on the header and the parent to do the appropriate positioning math, and useEffect to set up and tear down the event listner.

End result for the Proof of Concept:

  const headerRef = useRef<HTMLDivElement>(null)

  const adjustHeaderPosition = (headerRef?: HTMLDivElement | null) => {
    if(! headerRef) return;
    const headerRect = headerRef?.getBoundingClientRect();
    const headerHeight = headerRect?.height || 0;
    const parentRect = headerRef?.parentElement?.getBoundingClientRect();
    const parentTop = parentRect?.top || 0;
    const parentHeight = parentRect?.height || 0;
    
    if(parentTop !== undefined && parentTop < 0) {
      let topLocation = parentTop * -1;
      if(topLocation + headerHeight > parentHeight) {
        topLocation = parentHeight - headerHeight;
      }
      headerRef.style.top =`${topLocation}px`;
    } else {
      headerRef.style.top =`0px`;
    }
  }

  useEffect(() => {
    function onScroll() {
      const header = headerRef?.current;
      adjustHeaderPosition(header);
    }
    window.addEventListener("scroll", onScroll);
    return () => window.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <JobViewHeaderContainer>
    <JobViewHeaderContainer ref={headerRef}>

(the code is a little careful so as to avoid lint "this could be null!" warnings)

So it makes sure the bottom of the header doesn't go beyond the bottom of parent, and the overall effect is decent.

Thursday, November 3, 2022

click to copy to clipboard widget via javascript

On phones (at least iPhone, probably on Android), copy and pasting is a fiddly operation. (Heck I remember when the first iPhones didn't even have copy and paste, took them a while to get the loupe ui for that right)

One convenient widget I've seen adds a "click-to-copy" widget, like here's one for branch name in git:

I whipped up a version at jsitor.com but it was a little easier to share this codepen....

Here's the JS:

const makeCopyToClipboardWidgets = () => {
  document.querySelectorAll(".click-to-copy").forEach((el, i) => {
    const widget = document.createElement('a');
    widget.innerHTML = '⧉';
    widget.className = 'click-to-copy-widget';
    widget.addEventListener("click", () =>{
      const text = el.tagName === 'A' ? el.href : el.innerText
      navigator.clipboard.writeText(text);
      widget.classList.add('active');
      setTimeout(() => { widget.classList.remove('active');}, 500)
    });
    el.after(widget);
  });
}
document.addEventListener('DOMContentLoaded', makeCopyToClipboardWidgets);

And a little CSS flavoring:

.click-to-copy {
  border: black 1px dashed;
}
.click-to-copy-widget {
  cursor: pointer;
  position: relative;
  top: -4px;
  font-weight: bolder;
  font-size: 1.2em;
}
.click-to-copy-widget.active {
  color: red;
}

So then any DOM object of class "click-to-copy" will get a border and a little icon to click:

(pardon my developer junkbox content)

Some details:
  • I dug around for the Unicode U+29C9, "Two Joined Squares" for the icon
  • I add a little "active" class and the remove it half a second later to provide some visual feedback
  • after I finished the first version, I realized a common usecase is when you want to copy a link - especially useful because it's all to easy to accidentally follow the link as you're trying to copy it on a phone. But sometimes link have a different caption than the content that is worth copying (leaving out "https://", for instance, to make it cleaner). So my code special cases A tags, and grabs the href for them instead of the text...
So, pleased with the outcome. "Vanilla JS" solutions work well for me, because there's a lot on my sites that don't have a node-ish build process... but I admit i still think a bit in terms of jQuery, so that You Might Not Need JQuery page is still a godsend, even though I haven't used jQuery in a while... it was just such a clean syntax for element selection, adding classes, and putting stuff on "ready"...


Monday, October 24, 2022

yeah but it's not CHEATING cheating right?

 I tend to play a daily wordle, comparing notes with coworkers. 

Every once in a while I have 3 letters green, but no idea what goes in the other two places. Small enough that it's possible yet annoying to run the permutations in my head. So earlier I would write little scripts to do that which I finally decided to enhance with a UI...

it was interesting to generalize the hardcoded cases from earlier:

function permutate(){

 const bads = window.bads.value.toLowerCase();

 const goods = alphabet.split('').filter(x=>bads.indexOf(x) === -1);

 const word = window.word.value.toLowerCase();

 const parts = word.split('_'); 

 if(word.length !== 5 || parts.length !== 3) {

    alert('expected word to be 5 letters with two _s');

    return;

 }

 const perms = [];

 goods.forEach(a=>{goods.forEach(b=>perms.push(`${parts[0]}${a}${parts[1]}${b}${parts[2]}`))});

 window.results.innerHTML = perms.join('\n');

}

Used fun shortcuts like an element with an id is available at window.id, and then it was another good case of fun with split/join/filter/forEach.

(Pushing to an array outside of the loop seems a little less graceful, but wasn't sure how else to handle the nesting of it.)

So it's definitely sort of cheating, though much less so than using one of the sites that knows the words in Wordle...

Friday, October 21, 2022

switching modes in gmail

 I've used gmail for a long time, since 2004. For a long time I really loved its "Important + Unread" vs "Everything Else" feature; that really got to the heart of what I wanted in a robot sorting my email.... "this is probably worth looking" vs "this can probably be skipped" (this is independent of gmail's spam folder.)

I stuck with it even after it adopted a new paradigm (I think I remember it lifting the feature from a different trial mail service) of sorting mail into categories - Primary, Promotions, Social, Update , and Forums. But I still preferred the simpler "look at this or don't".

Well, for the past few months the "Important" categorization has really been slipping. And I was started to feel overwhelmed, and "inbox zero" (even for just "important and unread") was getting harder and harder to achieve. 

So for a week or so I switched to the categories... and you know, I think it helps a lot. It feels like I'm spending less effort to get to a true inbox zero (not just "important"). 

It lets gmail sneak a few more ads in there, but considering how long I've been using them as a free service, it's not bad. And unlike many the privacy aspect doesn't bug me too much.

Sometimes I wish I had been more steadfast in using an email address I control vs handing out my gmail... (especially since I used the ridiculous semi-gamer-handle kirkjerk...)

Wednesday, October 19, 2022

js array functions are so pleasant

Like yesterday's use of reduce(), there is something absolutely pleasant about a nice little set of JS array functions (filter, map, reduce, join) - like a programmer's mental ASMR. (And use of fat arrow functions make it even cleaner...)

Here's a shtick I did for an overly simplistic "Convert To Title Case" function:

const titleCase = (words) => words?.split(' ')
      .map(word => word.substring(0,1).toUpperCase()
                   + word.substring(1))
      .join(' ');

console.log(titleCase(null));

console.log(titleCase("this  is a test"));

console.log(titleCase("ok")); 

Just so charming.

Tuesday, October 18, 2022

trivial javascript templating with reduce()

 At work we have a translation system that has strings like this:

"Welcome home, ${name}"

"Bienvenido a casa, ${name}"

"willkommen zu hause ${name}"

etc. 

Now those look like javascript template strings, but since it's at run time not compile, you can't just jam it in with backticks....(and also you probably want to have a little map of key value pairs to use)

Apparently other systems we use have a template system for the i18n stuff - you could just include a maplike object of key/value substitutions to make - but ours didn't have that and it wasn't clear it would be easy to include, given all the gatsby etc environmental weirdness it has to handle

Being a properly lazy programmer, I found this example of code to possibly steal. It can even handle stuff like 'Example with math: ${2 +2}' and get you that 4. But that seemed like overkill plus triggered my 'possible security risk' spider sense - that whole "new Function()" bit made me think I was too lazy or not smart enough to know if it was safe to use.

Really, I just had a Javascript 101 / interview question on my hand... "how can I replace all ${FOO} instances in a string with the corresponding values from a maplike object?". But one insight was, I shouldn't treat it like a fancy regex for every instance of ${ }.... I can just run against the keys of maplike thing that has the substitutions I care about.

Interestingly (to me) the best candidate array helper function was "reduce()", against the array of keys from the maplike - the initial value is the template, then the function takes in the template (with applied substitutions) as the previous value, and the current value is actually the key to do a replaceAll with... thus...

const templater = (message,replacements) =>
Object.keys(replacements).reduce((stringSoFar, key) =>
stringSoFar.replaceAll(`\${${key}}`, replacements[key]), message);

And with a little tester 

const translation = "${FOO} x ${FOO} y ${BAR} z ${FOO} omega FOO BAR";
const replace = {"FOO":"foofoo","BAR": "barbar"};

console.log(templater(translation, replace));

which gave me

foofoo x foofoo y barbar z foofoo omega FOO BAR

as expected

Thursday, October 13, 2022

automated visual regression testing across devices via browserstack and percy

 My company is looking into using Browserstack's Tool Percy for automating testing, plugging it into CICD so that PR's all do a snapshot audit across components and pages.

It's a win - and I love testing holistic final products, not reductionistic units, though I worry it doesn't cover interactions very well... 

Also I worry about the cost. One plan has 200,000 snapshots but I worry that might be exhausted more quickly than you expect, because of the geometrical permutations from different components and pages, different browsers and devices, and multiple PR (especially if each commit in an open PR retriggers the build process...) Feels like when you have a cellphone plan without quite enough data...

Friday, October 7, 2022

github note - ignore whitespace (now with UI bit)

Around 5 years ago I posted this tidbit. and it came up again today:

When doing a Pull Request via github you can add a ?w=1 to the end of a url e.g.  https://YOURCOMPANYSITE/BRANCH/pull/1785/files?w=1

I reposted after sharing it with a coworker who didn't realize it existed, and a different coworker mentioned there's a UI for it: (that forces a reload because it's just a wrapper for the URL)

anyway, this obviously makes it easier to confirm code like where you're making a split or other conditional test permanent, and the only difference is of a big code block is indentation from when it was a conditional...

Thursday, October 6, 2022

gatsby copy files as last step of build

My employer has the same site in different locales, served by one main Gatsby-powered project. 

We wanted to have a different "robots.txt" for each process.... our gatsby-config.js had something that LOOKED like it was copying robots.txt in particular, but our use of gatsby-plugin-copy-files wasn't actually doing anything... it was confusing because the file still seemed to be copied, but apparently as part of a general "copy from static/ to public/" function - and overall it was hard to track what Gatsby was doing when.

I didn't do a deep dive into Gatsby, but this github entry seemed to be covering similar ground... the secret was to make a gatsby-node.js, and then do this:

const path = require('path');
const fs = require('fs-extra');

const envLocale = process.env.GATSBY_LOCALE || 'en-us';

/**
 *  Copy a locale-specific robots.txt file into place
 **/

exports.onPostBuild = () => {
  const src = path.join(
    __dirname,
    `/src/content/robots/${envLocale}/robots.txt`,
  );
  const dest = path.join(__dirname, `/public/robots.txt`);
  fs.copySync(src, dest);
};

It always feels fraught banging on tech someone else put into place, and you've never done a "hello world" with or anything. And gatsby is "interesting" especially in developer mode, where its often copying files over. But this seems to get the job done.

Saturday, October 1, 2022

best you may not need jquery - document ready

 I have long appreciated the site You might not need jQuery - it does a great job of bridging the gap from jQuery to vanilla.js. (The two are really quite similar... for my money the biggest similarity is "You don't need a build system".) 

One thing I look up a lot: document.ready

$(document).ready(function () {});

which can become

function ready(fn) {
  if (document.readyState !== 'loading') {
    fn();
  } else {
    document.addEventListener('DOMContentLoaded', fn);
  }
}

More as I end up using it...

Monday, September 19, 2022

happy 40th birthday of the smiley

Happy 40th Birthday of the Smiley!!!

 


Of course the classic punctuation smiley has been supplanted by cartoon-ish emoji. I admit the cartoons are more expressive and flexible (though sometimes you have to brace yourself for how they will have a different vibe on different devices) but I miss the old visual puns.

These are said to be the Top Five Favorite Emoji in the United States

But...bleh, I kind of hate both of the "laugh crying" emoji. It's just... I don't know, too much, it overdoes the thing. I use plenty 😃 to round off the edges of my casual writing, but the laugh-crying ones are trying to hard, like they seem dishonest, the LOL of the emoji world...

More on the history of the smiley at Lunduke

Friday, September 16, 2022

blue letter-y thing on white icons are so tired!

I saw this on the "this date" feature of my blog with the caption "Oh Shazam, you too?"


I was startled to see that was six years ago. And also, the one that causes me the most havoc in terms of quick recognition is "VS Code" on my Mac:

Though Outlook doesn't help:


Blue letter-y thing on white is so tired!

It's worse on Mac, actually... I see all the icons lined up on my switcher app, but the order is variable, unlike on the iPhone where I tend to develop muscle memory about where icons live.

Sunday, September 11, 2022

chilling out on the iceberg of languages

Great video about the languages out there...

After watching it, and it's reference to "Jr" programmers, I feel a little better as a senior, seeing just how many of these I've used. (BASIC, JS, SQL, Java, C... even ASM for my Atari 2600 project.) 

A few personal historical notes from the 90s (and from the 80s):

  • They missed out on Logo, huge in the 80s, which was lowkey a (kid friendly!) version of Lisp and featured turtle graphics, helping kids think about geometry via a drawing "turtle"
  • I feel like Java hit me at my peak learnability so I'm always surprised when it's considered so daunting. (Admittedly I kept using it in its friendly "Processing" form, and ducked out when the syntax started getting weird and Template-y)
  • It took a weirdly long time for C++ to standardize its libraries for even basic input and output.
  • I think they skipped over Perl? Probably its obliquely referenced in the "Historically Important Row" with the O'Reilly Camel. For me it was critical for learning things like maps and regexes when all I had had was BASIC and C... then it became my goto for dynamic stuff on websites I made, til PHP came around. (And I was started to learn that Perl was actually a wrapper script for all this Unix-C stuff... really weird given how smoothly it handled memory and strings and how its hodge podge syntax was the only hint to its frankenstein nature...)

Still, I don't have a love of learning a language for its own sake, like for keeping the brain flexible about syntax, say. I just want to build interactions and so there are more important problems at hand!

Tuesday, September 6, 2022

the dataLayer, and using local storage for cross-tab communication

 Just the other week I started looking more closely at the "dataLayer" - it's a standard variable off of window that Google Analytics uses to let processes record analytics events.

It seems like a strange way to do an API, especially since the data doesn't need to have identical structures, and it's just a big array like object you push on to. But it seems to be kinda standard in the industry thanks to the prevalence of Google Analytics.

In other learnings, I was impressed by the results my company was getting using localStorage to communicate some state across tabs/windows, in React the setup was something like 

  useEffect(() => {

    updateFromLocalStorageData();

    const storageListener = (e: StorageEvent) => {

      if (e.key === KEY_FOR_DATA) {

        updateFromLocalStorageData();

      }

    };

    window.addEventListener('storage', storageListener);

    // deregister event listener on re-render

    return () => {

      window.removeEventListener('storage', storageListener);

    };

  }, []);


using shortcuts to open google doc in app right from ios homescreen

Melissa was psyched to figure find this out on her own - we use a shared google doc for a shopping list, and she wanted a link right it from her iPhone homescreen.

The steps, taken from apple.stackexchange.com/questions/285696/create-google-sheets-shortcut-in-homescreen, are:

  • Open the document in GoogleDocs app on your iPhone.
  • Tap the three dots in the top corner
  • Share & export
  • Copy link
  • Leave the GoogleDocs app and open Shortcuts app
  • Create Shortcut
  • Add Action
  • Search for Safari
  • Open URLs
  • Tap the little gray field that says URL
  • Type googledocs://
  • Tap and paste (the link you copied from GoogleDocs). The result will look something like http://googledocs://https://docs.google.com/document/d/bunch-of-random-characters
  • Tap where it says “New Shortcut” at the top in big bold letters and name the shortcut
  • Tap the three dots
  • You can change the icon (the gray square with the magic wand in it) to what you want and give it a shortcut name.
  • Tap Add to Home Screen
  • Add
  • Done
  • Done/Next
Pretty keen!

The Daring Fireball's The Talk Show had Rosemary Orchard on talking about her book Take Control of Shortcuts, and she says there are tons of things to automate. I'm not sure if it's my lack of imagination, the way I am pretty good at building tools for my own workflows, or a distrust of formalizing activities that sometimes ned flexibility, but I have trouble thinking of more I'd like to do for myself, on either phone or laptop...

Tuesday, August 30, 2022

i can has :has?

 Man, the new :has pseudo-class sounds pretty great! Unfortunately browser coverage is poor. Still the ability to do a query based on the children in it, like 

figure:has(figcaption) {

  background: white;

  padding: 0.6rem;

}

seems awesome

Somehow it reminds me of James while John had had had had had had had had had had had a better effect on the teacher....

Monday, August 29, 2022

iOS tip of the day: view JUST photos or JUST videos in Photos app on iPhone

TL/DR: iOS Photo Albums have a "..." menu item including "Filter" for "Videos","Photos","Edited", and and "Favorites"

I've been doing 1 Second Everyday for years now, so I impulsively take a lot of quick snippet video footage - but when I'm browsing for post-worthy photos on my phone, I don't really want to see all the videos.

I assumed this would be accomplished via some kind of smart folder or album, but according to r/ios the best path is to just use the filter under the "..." menu that each album holds. A little fiddly but good enough,

Thursday, August 25, 2022

cheaters never prosper but sometimes we have more fun at wordle

 So, Spoilers for Aug 25's Wordle....

Here was my share:

Wordle 432 4/6
⬜⬜⬜⬜⬜
⬜⬜🟩⬜⬜
🟩🟩🟩⬜⬜
🟩🟩🟩🟩🟩

So had the first 3 letters... and knew I had all the vowels after step 2... but I wasn't seeing the answer and running all the permutations seemed tiresome, so I hopped over to jsitor (better than codepen as a js sandbox I think) and came up with

const letters = 'qwopfghjlzxcvbnm';
letters.split('').forEach(a=>{letters.split('').forEach(b=>console.log(`clo${a}${b}`))});

Yeah, the answer was "clown". Darn w-blends :-D I knew it as soon as I saw the second entry "cloqw"

Definitely not as cheat-y as going to a specialized website or even using /usr/dict/words , and actually I prefer a little programming exercise to gutting it out when the answer isn't showing up for me.

Thursday, August 18, 2022

give 'em the hook

 In one of those - am I being smarter or dumber than the person whose code I'm looking at, I was examining a big custom hook, and trying to figure out why it was a hook and not just a function. I was stackoverflowing react custom hooks vs normal functions, what is the difference and found a pretty good video about hooks in general, and making a case they can be a worthwhile layer for business logic apart from the UI:

Monday, August 15, 2022

the blind helping the blind

THE HIDDEN HISTORY OF SCREEN READERS ("For decades, blind programmers have been creating the tools their community needs")

Great read, especially for learning about JAWS vs NVDA.

For me, cognition is so entwined with vision, which in turn is entangled with how I approach programming, that it's daunting to consider a life not laid out on a 2D surface I can quickly skim...

Friday, August 12, 2022

the old palm, the new palm

 I loved my old Palm Pilot - I never had a Handspring Visor (and kind of disliked the design of them) but man, was it ahead of its time...

Great video. Also check out CloudPilotEmu for some impressive in-browser emulation.

websites for decades

 Nice post on what it takes to run a fansite across decades.

I've been running the Blender of Love since the mid-90s. It's now a bit of a zombie but still gets the occasional new piece... and my blog has been going since the last days of the year Y2K. But I've sort of lost my audience. And like I don't even know if being less mobile friendly has hurt my google juice..but I've never been about that life anyway.

"simplify simplify simplify" - should that be just "simplify"

How to OVER Engineer a Website... I really need to get better at deploying personal projects beyond just uploading stuff to by VPS...     

Wednesday, August 3, 2022

the swagger of the right apps and the right interface

Admittedly, this probably reflects a bit of a "slow season" for my devblog (not that I have much of a permanent audience base), but I just want to pontificate more on the apps I use for my personal information management, and wouldn't even mind hearing from others (kirkjerk at gmail dot com)

Recently I wrote about switching the core of my task tracking from 2Do (iOS + Mac, but the synching via dropbox was spotty at best) to the app Tot. Besides the superior synching between laptop and phone, I'm really appreciating the "freeform" aspect a text-ish document offers for sublists and task order. 2Do was special in presenting color-coded categories for tasks, and having contents of all categories viewable in a single scrolling list, but didn't have great facility for manually reordering a list or easily changing creating and changing and reordering categories. I do plan to continue using it for recurring and due-date based tasks.

The temptation to hunt for "one app to rule them all" is great, but I think there's much to be said for different apps for different kinds of information recording. Besides different apps having various strengths and weaknesses, the visual differences between apps aids in mental "muscle memory". (I think this was why Steve Jobs was fond of textures in apps, like that provided by skeuomorphism - it adds a flavor, or mental hook, to raw information)

Like when the app gets just the right fit, it has almost a certain swagger for me (albeit... the nerdiest possible kind of swagger). That's amplified somehow by having open windows across my three screens at my home office, and switching between them w/ keyboard controls.

Apple Notes: love this for work notes. I think it synchs well, to devices  and you can embed images and what not. Somehow I don't have a good feeling about Apple managing my data as a forever home, but work notes is generally not needed on that kind of scale.

Simplenote: excellent synching and search, and cool in a minimalist text-only way. This is sort of my catchall, and my using the text string FINDME as a casual tag, I've stopped trying to keep it at all sorted... it really akin to a big pile of stickies. ("Bear" was another similar app with more of a focus on markdown text)

Tot: as mentioned great for my main Todos, with its 6 extra notes used for various "To Get Tos" (music to get, books to check out, movies and shows to watch, games to play, etc)

2Do: Still like this for recurring todos, daily or weekly tasks or other time sensitive stuff. (Appigo Todo was pretty good as well, but didn't have as rich a sense of categories.) The free synching via Dropbox always felt slow, so I didn't keep using the desktop client for it.

Apple + Google Calendar: Google seems to be the easiest way to share a calendar - huge in a relationship. And I can import work's Outlook Calendar into it as well

k/db: this is a weird little homebrew database I made for keeping track of stuff like "media I've consumed" and "old coworkers"

But it's funny even with all these systems I always know what's what and where to go to look for stuff.



Saturday, July 23, 2022

todo'ing better

I was listening to a podcast with Oliver Burkeman, about time management.

While has some suggestions for "todo lists", his main point is more existential; that it is folly to hope you'll get to do all the things you'd like to in life, and maybe misery to try. (The faint and somewhat bitter silver lining to that is "maybe it's the finitude of life that gives our choices value". I'm a little suspicious that's just sour grapes.)

He also makes some good (if oft made) points about procrastination and distraction; most of us look to distractions to divert us from the emotional discomfort of the task at hand. (I know for me it's "this task is either tedious or challenging of my ego".)

But that all this is kind of a tangential framing to my main point, that I'm pysyched about my plans to switch my Todo systems up a bit.

Even back in 2006 I was cataloging systems I was dabbling with: PalmPilot, stickies and a spindle, small text files, whiteboard, graph-paper-sheet-a-day,etc.

For a number of year I've been happy with the app "2Do". It did a great job with two things a lot of similar check list apps lack: finegrained recurring events (like being able to specify if something is due every X days, like a bill, or if it's to be done Y days after you last did it, like a haircut) and then categorizing todos, but having the todos for all categories on a single big list, scrollable and so entirely viewable without clicking.

(I supplemented that with a homebrew daily chore list webapp I made, so that the daily grind wasn't blocking the other stuff so much.)

But 2Do had poor syncing (I shelled out for the pricey desktop version, but the syncing was surprisingly meh) and "tap to mark done" isn't THAT viscerally satisfying.

I think I will swing over to using the app Tot. It's a curiously minimal (but reliably syncing across iOS and MacOS, as is Simplenote) set of 7 color coded notes that I've been using to track music to get and shows to watch for a while.

I think having it always at hand on desktop or handheld will help, and that free form text will flow stuff more naturally in and out of other apps.

I might stick to 2Do for the repeating stuff (and maybe move the daily grind stuff back into it, so that I'm checking into 2 apps rather than 3...)

But I'm psyched about this. I've felt dissatisfied with my Todo system for a long while. (Also if you see a lot of random content on my blog and FB, that's why, I'm clearing the deck of old "Oh I should blog that" Todos...)

Friday, July 22, 2022

tweet-size coding

 

I'm not going to do the same deep dive breakdown I did of this p5 code last year - and honestly it's not quite as impressive, but I like how this tweetsize p5 uses arithmetic for a nice visualization:
t=0
draw=_=>{
t+=1
s=48
createCanvas(w=432,w)
background(0)
strokeWeight(2)
colorMode(HSB,99)
f=0
for(c=0;c0)line(c+i,r,c+i,r+s)
else
line(c,r+i,c+s,r+i)
}
}
}
//#p5t #つぶやきProcessing

Tuesday, July 19, 2022

startling tech

I was thinking about what technologies really startled me, in a "they an do that now?" sense.

A very incomplete list, in rough chronological order:
  • Google. It snuck up on me as the fallback search for Yahoo back in the day. When I realized it did a much better job of searching my site than my own homebrew search functions... and it was doing that for, like, every site on the web?
  • iPod. Again, a matter of scale: 1,000 songs in your pocket is just amazing.
  • Youtube. Yet another bit of astonishment at the scale of it. Hosting that much video for free was just mind blowing.
  • Google Maps. This was a minor thing, but the navigation it provided in browser, these large seamless draggable and zoomable tiles... amazing
  • On-dash GPS. I remember going to the video game con "PhillyClassic" and the guy driving had one of these. What a miracle of making life easier.
  • Accurate Video Game Emulation - especially in-browser.
  • AlphaZero. I always said I would be impressed when a good chess playing program was also good at another game - like it formed its own intelligence about a game, as AlphaZero did with Go and Chess. (Now the bar is "when will the computer be BORED of playing chess")
  • GPT-3 etc text generation. The "Ghostwriter" segment of The Ghost in the Machine episode of This American Life just blew me away... and you can find instances of it online. The ability to generate text that seems to have a point of view.
  • DALL-E etc art generation. Boy O Boy. Making illustrations from arbitary text prompts...
Those last 3... man.

One model of intelligence is that it's all metaphors and connections. And computers are really getting there.

I think the future of creative and information based jobs will be learning how to harness these kind of forces. And that's a little scary- both from a "future of work" standpoint, and also because so far I'm not sure if the machines will get better at showing their work, explaining their reasoning... (and right now they sometimes exhibit the racism etc implicit in their training data sets...)




tracking the trackers

 So, having to track down some weird issues with Google Analytics I learned about the tracker-trackers GTM/GA Debugger and Omnibug chrome extensions. Omnibug feels a little friendlier.

Friday, July 15, 2022

focus on screenreaders

Deque U's  Screen Reader Keyboard Shortcuts and Gestures.

It's easy for a dev to forget that just because you made your forms tab-navigable and keyboard friendly, your overall content might not be great for screenreaders: at a minimum screenreaders have keyboard controls for a sense of "focus" of what's being read, not just what is interactable. (Also many folks using assistive technology will use the "rotor" (which I wrote about a few years ago) which is why it's important to get your Heading elements etc in a decent hierarchy. 

Saturday, July 9, 2022

summer breaks

 Hola, amigos. It's been a long time since I rapped at ya but...

(For some reason whenever I'm getting back into something, I think of the traditional intro for Jim Anchower editoirials on "The Onion")

So I don't purposefully have a "summer break" mode, but that seems to be what's happening for a few activities... far fewer blog entries here, less new music acquired, fewer photos taken, fewer books read. I'm not quite sure why.

One probably minor aspect has been a bunch of work needed for my Porchfest websites. One of the most glamorous is Fenway Porchfest.

They're one of the ones that request a printable version, plus they have tons of sponsor/site host logos and what not. I find it easiest to do it via Print Design in the Browser -- you can see the outside and inside of the trifold pamphlet and then I take a screenshot. Definitely some compromises, but it syncs better and with less manual labor than a pure "in Illustrator" or whatever version (though some parts of it are images)

outside
 
inside

It's kind of nice to be able to tweak things by hand, not worry about responsive design etc...



Friday, July 1, 2022

quick reminder to self: how to sort array by a field in the map-ish objects in php

Just one of those small things I was surprised I hadn't already noted on my devblog, in PHP, to sort an array of objects / array of maps, based on a field in all the maps, it's the usort command which works in place on the array, and with the second argument being (weirdly) a string version of the function name:

    usort($allresponses,"sortOnFirstField");

     function sortOnFirstField($a,$b) {

        global $firstkey;

        return strcmp($a[$firstkey],$b[$firstkey]);

    }

The weirdness of using a stringname... definitely oldschool. Javascript is obviously a bit slicker, though I still appreciate how PHP splits the differences with globals that I have to use instead... (YES you can use them, but also YES you have to acknowledge that you are using on in a function)


Oh and while I am at it, replace special characters etc in a string:

function clean($string) {

   $string = str_replace(' ', '-', $string); // Replaces all spaces with hyphens.

   return preg_replace('/[^A-Za-z0-9\-]/', '', $string); // Removes special chars.

}

Thursday, June 30, 2022

what's new in js

Heh, you know there are two things that tend to clog up my inbox (because I get stressed out and/or feeling like I should get to it later, but then it just sits) -recruiter contacts and Javascript newsletters.

My favorite of the latter are Frontend Focus and Javascript Weekly.

Here's some stuff that came up:

What's new in the 2022 ECMAscript standard...

Object vs Map. The thing is, I'm not sure devs are really willing to give up their syntatic sugar! I think that's why JS + JSON have been so killer, the concise syntax letting you put together arrays an objects with a minimum of fuss...

Defensive CSS seems pretty good and well-presented.

Tuesday, June 28, 2022

making pill tracking sheets in php

Our poor kitty Dean has a touch of small cell lymphoma (on top of his asthma) and his pill schedule has gotten pretty complicated! I decided to make a simple tool kirk.is/tools/pills/ that lets me (or anyone) make a printable pill schedule grid, ala:

(You can see Dean's full grid here)...

Like my recent quickpoll program, I decided to both keep it as pure PHP as well as relying a rather too geeky by half text-based format for the pill descriptors, each pill name can be followed by an @ sign to say how often (e.g. 2 for every other day, 3 for every third day, etc) and then a + sign and an offset for how many days forward):

everyday pill
every other day pill@2
every week pill@7
every 2 week pill, start in 5 days@14+5

I think I really need to build myself a generic form builder, while this text-block based stuff is cool in a minimalist way (and lets you copy and paste almost all the data in one fell swoop) it's not user friendly...

Anyway, other minor bits of cleverness is setting up the page to submit to itself via GET, so it's always bookmarkable, as well as using CSS media queries so when you print it's just the grid.

Thursday, June 23, 2022

worst java interviewee ever

In 2005 I posted this on my blog, quoted here for posterity:

A friend gave me a photocopy of a worksheet from a programmer job interview, the contents of which I'm transcribing here. Probably the worst interviewee experience I've seen. I've tried to be reasonably fair; arguably I should could be generous and use * in place of ·, since this was handwritten, but given all the absolute conceptual failures and mental disconnects, I'm leaving it as is.

So the interview "challenge" was to write a factorial function. Now, I don't know exactly how well or poorly the problem was described by the interviewer, but given the first line, probably copied from a whiteboard, I'm assuming it was pretty straightforward. Here's what the sheet had on it...most of the strikes are circular scribble-outs:

5! = 1·2·3·4·5

public string access(string 5! a)

{

  string

    string b = a.substring(0,1);

   string

    Int int c = b String.getValue(b);

    if(c < 2){

      System.System.err.println("1");

    } else if (c < 3){

      System.err.println("1·2");

    } else if (c < 4){

      System.err.println("1·2·3");

    }

For someone aspiring to a Java development position, and who must've sounded at least possibly decent on the phonescreen...supposedly the person has multiple masters degrees in science-y and computer-science-y fields, both from good local Universities.

For the non-techies on my blog I summarized the issues like this:

1. Returning a string? Already something seems a bit amiss.

2. Why name a function like this "access"?

3. Passing "5!" as a a variable name, even though struck out, is a telling mixup of variable name and content.

4. More string oddness. These two lines seem to indicate he's just trying to parse the first character of the String passed in to an int, ignoring the rest.

5. To the right of "c -", there's NOTHING about Integers...I'm willing to give him a pass in terms of specific function name (Integer.valueOf(String) would have "worked" as a replacement for String.getValue(String)) but he's not even in the ballpark.

6. The use of "if"s here, 3 conditional cases, is just an astounding example of misthink. Logically, only Strings starting with 0-3 will produce any output, and even then if it's a "one digit string" of the values 1-3 could this logic conceivably "work".

7. Finally, the use of "System.err" rather than "System.out" is just... well, wrong. Or maybe so right, a subtle bit of self-commenting on the error-ridden code...

An even half-way decent programmer when given this problem would likely think for a moment and then say "do you care if it's solved recursively or iteratively"? (With the latter being the better bet, performance wise, but that's a small issue). Also, this is kind of an "academic" problem, and the guy seemed to have a strong academic background...besides talking a *great* game about all the latest technologies, and getting some complex stuff about J2EE right. It's just really odd.