Monday, October 25, 2021

helmet - use arrays not fragments for repetitive blocks (ala hreflang)

Getting exposed to a few new things at work... I just mentioned the hreflang tag, but also on the React side we use Helmet, which seems a clever way of avoiding dueling, incompatible meta tags even if you have a weird bunch of components trying to put stuff up there.

I was trying put all the hreflang tags I had to make as a React Fragment (<></>) but they were getting eaten up - looking back I'm pretty sure that's because Helmet assumes all the stuff it needs to worry about is a 'top level' component. So instead of a Fragement, I had a function return an array of individual tags, and that seemed to work fine.

designer wisdom

Last February at my job, the designer Sous bestowed this wisdom:

When in doubt, 16px


hreflang

 Hadn't head too much about "hreflang", the meta tag to tell search engines etc "the equivalent of this page exists in locale X at URL Y" but this seems like a pretty thorough introduction.

when heroes look like they're breaking down

 I'm always kind of intrigued how every generation of Apple hardware gets a new set of background images, shots used on the glamour/hero product shots. But lately it feels like they may be running out of ideas? Like both the Macbook Pro and iPhone Pro resemble various ways I've seen screens fail because of physical impact....I've definitely seen screens like this:


And while usually the light-saber looking beam of a pure color extends the whole screen, seeing these beams zoom in as I scroll through the promo page took me aback:

Would love to see a gallery of these images over the years, and fund out about the process of selecting them... is it like an honor for one person, or by committee, or what.

Saturday, October 23, 2021

hothouse gullibility flowers

It's probably not great to attack a book when I've barely started it, but early in Chapter 1 of Steven Pinker's "Rationality", he mentions an old chestnut of a riddle
On a field there is a patch of weeds. Every day the patch doubles in size. It takes 30 days for the patch to cover the whole field. How long did it take for the patch to cover half the field?
and Pinker bemoans that most people won't get the correct answer (29 days, i.e. since it doubles daily, the day before the final day it was half the final size.)

He explains
Human intuition doesn't grasp exponential (geometric) growth, namely something that rises at a rising rate, proportional to how large it already is, such as compound interest, economic growth, and the spread of a contagious disease. People mistake it for steady creep or slight acceleration, and their imaginations don't keep up with the relentless doubling.
But what a fantastical setup that riddle is! Like any physical model would show us that no patch of weeds on earth could have that kind of behavior "steadily" over 30 days. To show that to myself, I hacked my version of Conway's Game of Life to be even simpler : every alive cell lives on, and every dead cell with at least one alive neighbor is born. The result is visually boring - a square that grows from the middle of the screen. And checking the population numbers, they are far from doubling. The rate that the square can grow is clearly bounded by its boundary, the 2D "surface area" where it has new fertile territory to move into, and so there's no way its actual area could keep doubling. And similarly, I can't think of a mechanism and environment that would support much of anything from having consistent doubling behavior for 30 days!

I find these thought experiments infuriating when they are used as examples of people's "irrationality". It's akin to economists thinking people are irrational for preferring receiving ten dollars now vs thirty dollars a year from now. In an uncertain world, any real world test subject is absolutely correct to be suspicious of a test program reliably running over the course of a year (especially when its business model seems to have big deal of just giving away money!)

I used to think of these as "casino-ish" problems- like, they are customized to prey on human's response at this attractive edge of artifice. But I guess I'd say they're "hothouse gullibility" thought experiments - they take for granted that OF COURSE the research is trustworthy, or that a patch of weeds that doubles every day for 30 days is a meaningful prototype to ponder. They are merely interrogating how well subjects can navigate a completely artificial environment of simplifying assumptions.

UPDATE:
Update... later in explaining why people make this kind of error he does say
we might point to the ephemerality of exponential processes in natural environments (prior to historical innovations like economic growth and compound interest). Things that can't go on forever don't, and organisms can multiply only to the point where they deplete, foul, or saturate their environments, bending the exponential curve into an S. This includes pandemics, which peter out once enough susceptible hosts in the herd are killed or develop immunity.
So I think it still forces the question: how meaningful are these contrived examples in generating useful knowledge about the world?

Wednesday, October 20, 2021

Saturday, October 16, 2021

10 Years of Devblogging!

Wow - I've been personal blogging daily since 2000 but in some ways I'm more surprised that this devblog is 10 today! Here's my first post.

So, no real intent of stopping, and I'll leave you today with this thought (aka the Robustness Principle) which I encountered on Usenet back in the 90s:

Be conservative in what you send and liberal in what you accept.
-- RFC 791, paraphrased 

My belief in the second part ("liberal in what you accept") is part of why I am less into TypeScript as some folks :-D Maybe the result of two and a half decades of Perl and Javascript.

Wednesday, October 13, 2021

creating and positioning circle image clipping masks in p5.js

 As I previously blogged, image clipping masks in p5 are possible, but not easy to work with.  Stackoverflow explained how to use offscreen graphics to allow "drawn" masks (vs using another image as the alpha channel) but that graphic is stretched to cover the whole image - and then when you position the now masked image, you have to deal with the width and height of the rectangle of the original image, even though you've clipped to some arbitrary part within that rectangle.

I thought I would simplify things by going for circles (vs my original idea of arches, which would be very trick to calculate bounding boxes for and reposition). Even then it wouldn't be trivial; for aesthetic reasons I might prefer a mask where the circle was on one side or the other of the original rectangle, and of course some images are "landscape-mode" and others are "portrait-mode":

It took me longer to wrestle with this problem than I want to admit (I mean not THAT long, but still) - a few false starts where I thought maybe I had to treat portrait vs landscape separately. But the "aha!" moments (more like, "oh, right...") came from noticing that A. for simplicity we are assuming that at least one of the sides of the original image is the size of circle B. we can figure out the "ratio" of how much larger the original image was than the circle for each side C. those ratios tell us how much we have to shift the positioning of the whole image in order to get the circle back to where we want it. 


(I actually added two masks for each image, one a half-filter for the whole image rectangle so we can see what the actual corners we have to work with are, and then the circle which is the end goal)

The heart of it is these two functions. The first one (I take care to preload() the images so we can get the reliably get the width and height of the raw images) makes the mask:

function makeMaskedImage(img, dir){
    const circleMask = createGraphics(img.width,img.height);
  // fill a soft mask so we see the whole frame,
  // for educational purposes
    circleMask.fill('rgba(0, 0, 0, .5)');
    circleMask.rect(0,0,img.width,img.height);
  
    const sz = min(img.width,img.height);
    const wRatio = img.width/sz;
    const hRatio = img.height/sz;
    
  //hard circle mask
    circleMask.fill('rgba(0, 0, 0, 1)');
    circleMask.circle(
        (sz * wRatio) / 2 + (dir * ((sz  * wRatio - sz)/2)),
        (sz * hRatio) / 2 + (dir * ((sz  * hRatio - sz)/2)),
    sz);
    img.mask(circleMask);
  return {img, wRatio, hRatio, dir};
}
"dir" -  a value between -1 and 1 representing the offset of the circle from the center to the edge for the longer side - is kind of an aesthetic choice for each image, so it's a parameter.  The calculated wRatio and hRatio are how long that side is relative to the size of the circle - so if the image was square to begin with, these would both be "1", meaning each side is as big as circle.

The math is fiddly, but basically says "put the circle in the center, then move it "dir" times one-half how much bigger that side is than the base circle.  We then return information we will need to properly display it: the image itself (now with the masked affixed to it), the two ratios, and the "dir" indication which way (and technically how much, tho mostly -1, 0, or 1) the circle is offset from center along the longest side.

The drawing code then is
function drawMaskedImage(maskedImg, cx, cy, sz, ){
    const {img, wRatio, hRatio, dir} = maskedImg;
    const trueWidth = sz * wRatio;
    const trueHeight =  sz * hRatio;
    const xCenter = cx - (trueWidth/2);
    const yCenter = cy - (trueHeight/2)
    
    const xWiggle = ((trueWidth - sz) / 2) * dir;
    const yWiggle = ((trueHeight - sz) / 2) * dir;   

    image(img,xCenter - xWiggle,yCenter - yWiggle,trueWidth,trueHeight);   
}
Elsewhere in the code we make sure we're in rectMode(CENTER); a bit easier to deal with the middle of the image than work from the corners. We formulate the whole rectangular images dimensions at this arbitrary size sz, figure out the x and y center as if the circle was in the middle of that rectangle, then xWiggle and yWiggle figure out how to counterbalance the positioning of the rectangle in terms of the circle relative to it.

Phew! I don't know if there's a more elegant way but this seems pretty good to me.

Random Tangent:
I'm currently trying to figure out what I can learn from people who LOVE strongly typed languages, much more than I do. I mean, I wouldn't mind using typescript to describe what keys should be in {img, wRatio, hRatio, dir}, but here's the thing: as I evolved this code, at first I thought "dir" should be a fixed value, like "LEFT" or "TOP" or "CENTER", but then realizing that could also be 1,0,-1 - and in those cases do double duty as a math multiplier. (In fact, my new system would let me do other offsets, in case you didn't want the spotlight quite bumping up against one edge or the other.) Like I suspect someone who wasn't comfortable with loosely typed languages (as I am after decades of Perl and then Javascript) would have noticed that flexibility and possible repurposing so easily.


Tuesday, October 12, 2021

canistilluse.com

 https://www.canistilluse.com/ - kind of a joke site, about the kerfuffle on deprecating certain hoary old things in browsers, like "alert()" boxes.

I hate that trend towards removing innocuous things in the name of progress. I'm coming to terms with how URLs aren't forever, nor websites, but going out of your way to break other people's stuff seems rude.

Google's Dark Mode

 At work our designer Dean mentioned:

Dunno if anyone took notice but Google Search recently released a Dark Mode (If that’s your jam, it is mine!)  which from a design POV was well done IMHO, good contrast but nicely nuanced meaning it’s not harsh but refreshing on the eyes because they didn’t go full black (an easy mistake to make) and chose softer colors  (also an in-thing to not go full bright color) to match 

I mentioned I personally am not a fan of dark modes (especially if they're used as a default) even though I know they are popular- but for people with Astigmatism - a big part of the population, they are problematic because the eye has to open more and that leads to blurriness for them Dean wrote back with

Ya agree, not for everyone. Just geeking and appreciating a good implementation of it as generally I think most folks think it’s a flipping of a switch but there’s actually more than less nuance in dark modes to deal with the accessibility issues, keep within the brand, consider dimensionality, statefulness, and elevations etc.

But they are popular enough that I have to assume it's not just "looks cool' and some people do find it more relaxing on the eyes - wonder if it's the sample people who prefer e-ink to reading on tablets with more visual "pop"


Understanding Figma Hierarchy

 Figma has a hierarchy, but it’s not always clearly represented in its web UI.

In particular, if you are looking at a specific “page” in Figma (for example, maybe a designer is updating a component, and you are a developer ) it might not be clear how to get to that page’s File’s Project ID. (For my team, Project IDs are very important for the tool we built to pull Figma data from its API.)

Empirically, Figma’s hierarchy seems to be:

Organization > Team > Project > File > Page

So for a Monster Global team member checking out some new changes to Buttons, that might be 

Monster (org) > Core (team) > Pre-Production (project) > Core [Jira Ticket Num] (file) > Button (page)

This hierarchy is not well represented by the Figma sidebar. For instance, logging in and heading back to figma.com/ I see this:


But often our Designers hand around links straight to Pages, and the Project ID isn't in the URL... in that case the stylized "F" has a menu with a "Back to Files" option that (if permissions are right and the creek don't rise) takes you to the page for the whole Project, which has the Project ID embedded in its URL.

Tuesday, October 5, 2021

form submit POST/GET echo

Awhile back I wrote https://stuff.alienbill.com/formecho/ as a simple "echo whatever POST or GET info was passed in" that came in useful at work today.

Thursday, September 30, 2021

css accent-color

CSS for accent-color looks pretty dope! I think the lack of reliable CSS styling on native form elements- an that goes WAY back to Web 1.0 days - has created so much bogus work making fakey components. 

jira: origins

Finding by our scrummaster Eric Stolls about Jira, a tool we rely on heavily:

 I stumbled across this today.   Fun Fact about the origin of the name Jira - The product name is a truncation of Gojira, the Japanese word for Godzilla. The name originated from a nickname Atlassian developers used to refer to Bugzilla, which was previously used internally for bug-tracking.

Heh, Bugzilla was probably a play off of the old "Mozilla". My  personal homepage is still called "Kirkzilla" as a play on that, back when it was just the html view of my Netscape bookmarks.

Friday, September 24, 2021

low-key but excellent improvement in iOS 15: calendar time editing

 Many people praise the return of the "maginify loupe" in iOS 15, letting you more easily see what you're highlighting just a bit a part from your big ol', but I just stumbled on an improvement I'm even more grateful for: iOS 14's calendar entry edit field was a mess. Here it is on iOS14 (on my sweetie Melissa's phone):


This is what you see in edit mode and go to edit the start time - a text-ish input for the time appears underneath what you clicked to edit it. I always found that unintuitive, to have 2 fields meaning the same thing, one editable - especially since it was easy to conceptually mix up with the other field beneath it, the one for End time.

But earlier I had been denoted that on my iOS 15 phone, the old "tumblers" are back!



I find those tumblers a small delight to use, just a nice little bit of kinetic action - and as you roll it around to find the hour, you get a sense of where you are are in a 12 hour period that you lack tapping in digits. Maybe more importantly, the value display is much more distinct from the value edit field. Win-win!


command line and text "fun"

In the so-old-school it’s new department, Command Line Interfaces are back - if they ever went away in the first place. 

Firebear Studio has a list of 75 Best Node.js Command Line Apps & Utilities. You can build some awesome into your installer, like parallel dynamic spinner/progress bars, giant ASCII font banners, and slick pick list and options sets.

Asciiflow is another text-based cool tool… it’s a simple diagram editor, but it outputs ASCII (both extended and old-school)…


From


to

+----------------+

|                |

|                |

|  Are we having |

|                +-----------------+

|     fun yet?   |                 |

|                |                 |

|                |                 |

+----------------+                 |

                       +-----------v-------+

                       |                   |

                       |                   |

                       |    Well Why Not?  |

                       |                   |

                       |                   |

                       |                   |

                       +-------------------+

Just make sure you are using a fixed-width font!


Tuesday, September 21, 2021

the size of boxes

Box-sizing is a minor annoyance for the modern web developer. In short, the question is “when setting the size of a DOM object, should you include the border and padding (“border-box” for the “box-sizing” property) or just the content “guts”? (“content-box”)

Historically, “content-box” was, and remains, the default, even though the consensus is “border-box” is easier to work with. So many sites will put something like

* {
  box-sizing: border-box;
}

So your design system needs to account for that - testing in an environment where that has been set. (Empirically it seems Figma uses the older content-box model.) 

  1. Right Click on an page and hit “Inspect”
  2. Under the Styles tab, hit the “+” button for “New Style Rule”
  3. Replace any class or element name with “*”
  4. Click to enter a new CSS property and enter `box-sizing: border-box;` resulting in something like 

The MDN box-sizing page goes into more detail on the options you have for it.

Monday, September 20, 2021

css resize property

You know, CSS resize property is pretty cool, for oldtimers like me it's one of those things that would have been a HUGE pain to try and implement in the browser, but now we get it for free:

resize: both;
overflow: auto;

I guess that's in the same family as setting the contenteditable property on a div:

This text can be edited by the user.!!

Obviously the DOM is so much more dynamic than it was back in the day, so it's great they added some hooks into that. (Editing the content of the div, basically making everything into a textarea... sort of weird but I guess not that much different than using developer/inspector tools

Thursday, September 16, 2021

phone goofiness and p5 instead of pixel editors

After getting a nice yellow silcone magsafe case for my iPhone, I decided to lean into the "duck" theme I stumbled on a few weeks ago:



The pixel duck was "inspired" by a google search for "pixel ducks", I then used browser-based tool piskel to roughly copy it (but using fewer colors.)

In making the actual background image, I was fumbling both with Acorn and Pixelmator... in both cases I ran into problems - either resizing without resampling, or just other basic functions. So I switched to a p5.js program. The "scale" trick to flip the image was a little wonky, and it took some finesse, but in general I like the control by doing design in code.

(Before this the theme was bumblebees, with an intermediate diver/blooper theme after I drowned buzzbuzz and had to get a new iPhone.)

Wednesday, September 15, 2021

WIP: permutation engine

A while back I showed a storybook component showing a 2D grid of combinations for props for a component library. V2 looked a little less cool but did a much better job of coming up with ALL permutations, not just the 1+1 combos you can show on a grid...



Here is the code:

import React from 'react';
import PropTestingProps from './permutation-testing.props';
import { PermutationTestingStyle } from './permutation-testing.style';
import { Button } from '../Button/button.component';

import { Checkbox } from '../Checkbox/checkbox.component';
import { Radio } from '../Radio/radio.component';
import styled from 'styled-components';
import { IconButton } from '../IconButton/icon-button.component';
import { Slider } from '../Slider/slider.component';
import { Tab } from '../Tab/tab.component';
import { TextArea } from '../TextArea/text-area.component';
import { TextField } from '../TextField/text-field.component';
import { Dropdown } from '../Dropdown/dropdown.component';
import { TypeAhead } from '../TypeAhead/type-ahead.component';

const Table = styled.table`
  th {
    font-weight: bold;
  }
  th,
  td {
    padding: 8px;
    font-family: sans-serif;
    vertical-align: middle;
    text-align: left;
  }
  margin-bottom: 16px;
`;

let count = 0;
interface ComponentPropMatrixProps {
  label: string;
  permutations: string[];
  render: React.FC;
}

const getAllSubsets = (theArray: string[]) =>
  theArray.reduce(
    (subsets, value) => subsets.concat(subsets.map((set) => [value, ...set])),
    [[] as string[]],
  );

const ComponentPropSubsetsList = (props: ComponentPropMatrixProps) => {
  const { permutations, render, label } = props;

  // get all subsets, sort on length of subset, then keep original order of elemts
  const subsets = getAllSubsets(permutations)
    .sort((a, b) => a.length - b.length)
    .map((arr) =>
      arr.sort((a, b) => permutations.indexOf(a) - permutations.indexOf(b)),
    );

  return (
    <>
      <h3>{label}</h3>
      <Table>
        <tbody>
          {subsets.map((subset) => {
            const subsetAsProps = {};
            subset.forEach((x) => {
              subsetAsProps[x] = true;
            });
            count++;
            console.log(count);
            return (
              <tr key={JSON.stringify(subset)}>
                <td>{render({ componentProps: subsetAsProps, label })}</td>
                <th>{subset.join(' ')}</th>
              </tr>
            );
          })}
        </tbody>
      </Table>
    </>
  );
};
// idle, checked, error, disabled

const buttonVariations: React.ReactChild[] = [];
for (const shape of ['rectangle', 'oval']) {
  for (const size of ['sm', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      console.log(`${size} ${shape} ${ord}`);
      buttonVariations.push(
        <ComponentPropSubsetsList
          label={`Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <Button
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Button
            </Button>
          )}
        />,
      );
    }
  }
}

const checkboxVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  checkboxVariations.push(
    <ComponentPropSubsetsList
      label={`Checkbox ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Checkbox size={size} {...componentProps} label='Checkbox' />
      )}
    />,
  );
}

const iconButtonVariations: React.ReactChild[] = [];
for (const shape of ['square', 'circle']) {
  for (const size of ['sm', 'md', 'lg']) {
    for (const ord of ['primary', 'secondary', 'tertiary']) {
      iconButtonVariations.push(
        <ComponentPropSubsetsList
          label={`Icon Button ${size} ${shape} ${ord}`}
          permutations={['disabled']}
          render={({ componentProps, label }) => (
            <IconButton
              size={size}
              shape={shape}
              buttonType={ord}
              {...componentProps}
            >
              Icon Button
            </IconButton>
          )}
        />,
      );
    }
  }
}

const radioVariations: React.ReactChild[] = [];
for (const size of ['sm', 'md']) {
  radioVariations.push(
    <ComponentPropSubsetsList
      label={`Radio ${size}`}
      permutations={['checked', 'error', 'disabled']}
      render={({ componentProps, label }) => (
        <Radio
          size={size}
          {...componentProps}
          id={`${JSON.stringify(componentProps)}_${size}`}
          label='Checkbox'
        />
      )}
    />,
  );
}

const sliderVariations: React.ReactChild[] = [];

sliderVariations.push(
  <ComponentPropSubsetsList
    label={`Slider`}
    permutations={[]}
    render={({ componentProps, label }) => (
      <Slider {...componentProps} label='Slider' />
    )}
  />,
);

const tabVariations: React.ReactChild[] = [];

tabVariations.push(
  <ComponentPropSubsetsList
    label={`Tab`}
    permutations={[]}
    render={({ componentProps, label }) => <Tab {...componentProps}>Tab</Tab>}
  />,
);

const textAreaVariations: React.ReactChild[] = [];

textAreaVariations.push(
  <ComponentPropSubsetsList
    label={`Text Area`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextArea {...componentProps}>Some Text</TextArea>
    )}
  />,
);

const textFieldVariations: React.ReactChild[] = [];

textFieldVariations.push(
  <ComponentPropSubsetsList
    label={`Text Field`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TextField value='Some Text' {...componentProps}></TextField>
    )}
  />,
);

const dropDownVariations: React.ReactChild[] = [];

dropDownVariations.push(
  <ComponentPropSubsetsList
    label={`Dropdown`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <Dropdown value='Some Text' {...componentProps}></Dropdown>
    )}
  />,
);

const typeAheadVariations: React.ReactChild[] = [];

typeAheadVariations.push(
  <ComponentPropSubsetsList
    label={`Type Ahead`}
    permutations={['disabled', 'error']}
    render={({ componentProps, label }) => (
      <TypeAhead value='Some Text' {...componentProps}></TypeAhead>
    )}
  />,
);

export const PermutationTesting = (props: PropTestingProps) => {
  return (
    <PermutationTestingStyle>
      {buttonVariations}
      {checkboxVariations}
      {iconButtonVariations}
      {radioVariations}
      {sliderVariations}
      {tabVariations}
      {textAreaVariations}
      {textFieldVariations}
      {dropDownVariations}
      {typeAheadVariations}
    </PermutationTestingStyle>
  );
};

As maybe you can see it's pretty easy to spin up new variants. "getAllSubsets()" is some very clever code I found online and honestly don't quite understand that makes all the permutations possible.

Monday, September 13, 2021

stuntcoding and the power of sin (and cos)

 A while back I made this (somewhat pretentious) business card:

It has a small Processing program on the back - in 2014 I impressed Ben Fry, co-founder of Processing, by having it there.

You can see the program in action at kirk.is/card/. The code is 
//spiro.pde - see processing.org
float a,b;
void draw(){
  noStroke(); fill(0,10);
  rect(0,0,100,100); fill(255);
  ellipse(50+(20*cos(a)+10*cos(b)),
          50+(20*sin(a)+10*sin(b)),
          3,3);
  a += map(mouseX,0,100,-.5,.5);
  b += map(mouseY,0,100,-.5,.5);
} //see in action: http://kirk.is/card/
It's nothing too fancy... just using the mouseX and mouseY as speed controls for a pair of nested circles - very much like a spirograph. ("draw()" is the p5 function called every tick of the clock)

One clever trick is rather than erasing the window each click, it puts a rectangle over of black at 10% transparency, leading to a nice little fade out effect and letting the user see the pattern as it is made.

But then the other day I ran across this beauty at fyprocessing on tumblr: (written in p5, processing's Javascript descendent)

which is made from this snippet, that fits in 1 280-char tweet (barely!)
t=0
draw=_=>{createCanvas(w=500,w)
n=sin
t+=.03
for(c=0;c<w;c+=31)for(r=0;r<w;r+=31)if((r+c)%2<1){beginShape(fill(0))
for(i=0;i<4;i++)for(b=i*PI/2+max(min(n(t+dist(w,w,c,r))*1.5+n(t/2)/2,1),
-1)*PI,a=b+3.9;a>b+2.3;a-=.1)vertex(c+cos(b)*32+cos(a)*18,r+n(b)*32+n(a)*18)
endShape()}}
Here's the tweet that it came from.  You can see it in an editor here.

That is amazing and beautiful, and I didn't understand it all. First I need to start to unobfuscate it: (to be fair it wasn't obfuscated to make it confusing, just concise.)

My first step was to just reformat it and change just a few of the syntatic shortcuts:
let t=0;
function draw(){
  createCanvas(w=500,w);
  let n=sin;
  t+=.03;
  for(c=0;c<w;c+=31){
    for(r=0;r<w;r+=31){
      if((r+c)%2<1){
        beginShape();
        fill(0);
        for(i=0;i<4;i++) {
          for(b=i*PI/2+max(min(n(t+dist(w,w,c,r))*1.5+n(t/2)/2,1),-1)*PI,
          a=b+3.9; a>b+2.3; a-=.1) {
            vertex(c+cos(b)*32+cos(a)*18,r+n(b)*32+n(a)*18);
          }
        }
        endShape()
      }
    }
  }
}
I swapped in `function draw()` for `draw=_=>` - I was looking up what "_" meant before I realized it was just a throw away variable for the fat arrow notation. 

He also uses "n" as a shortcut for "sin". I'm going to undo that, and then change the complicated for() loop into its "while" equivalent.
let t=0;
const SZ = 500;
function draw(){
  createCanvas(SZ,SZ);
  t+=.03;
  for(c=0;c<SZ;c+=31){
    for(r=0;r<SZ;r+=31){
      if((r+c)%2<1) {
        beginShape();
        fill(0);
        for(i=0;i<4;i++){
            b=i*PI/2+max(min(sin(t+dist(SZ,SZ,c,r))*1.5+sin(t/2)/2,1),-1)*PI;
            a=b+3.9; 
            while(a>b+2.3) {
              vertex(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18);
              a-=.1;
            }
          }
        endShape()
      }
    }
  }
}
Also I replaced the cleverly initialized "w" variable with a big old const SZ, my favorite shortcut for the size of a thing. 

So now I'm at a place to start poking at variables. "t" seems to be the basic timer; by incrementing by more per click I can speed it up, for instance. Or make it stay at 0.0 -- things are frozen (but when you freeze time like that, many of the spinners are at odd angles... interesting ) And it's pretty clear "r" and "c" are for rows and columns - incrementing more than that "31" spaces them out.  

So we're making a bunch of shapes. 
      if((r+c)%2<1) {
is making it like a checkerboard pattern... I added
fill(200,0,0); circle(c,r,10);
after the EndShape and then I can see a the center of each formation.

So now, looking at the code... the loop with i is being done 4 times.

To try and make things more clear, I replace the vertex() in the shape
    vertex(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18);
 with a tiny box, so I can see whats being drawn:
    rect(c+cos(b)*32+cos(a)*18,r+sin(b)*32+sin(a)*18,1,1);
 but it doesn't help me see much, I guess things are tightly packed within the shape. I see a is being adjusted by .1 each iteration... bumping that up to .3 and the way things are drawn is more clear, especially with my dots placed at the center of each thing.
So a is iterating to draw one of the 4 sides. It starts at whatever b is plus 3.9, and then goes down to b plus 2.3... in other words, ranges PI * ( 5 / 4) down to PI * (3 / 4)

So I put the vertex() line back, because now I roughly get it what's going on with the draw shape stuff. There's still some "spirograph" cleverness with a big circle plus a small circle (and at related angles) but still.

If I throw away b's fancy computed value and replace it with  b = i * PI/2; I see everything locked down- and I can add a constant to that see all the widgets move at the same angle, and 
b = mouseX / 10 +  i * PI/2;
is a very nice effect I can control. (remember, i is just 0, 1, 2 and 3, so we're just doing 4 sides of the circle, so to speak.)

So that just leaves us with this monster that effectively sets b for each spinner to a constant (outside of the i * PI /2 boost):
max(min(sin(t+dist(SZ/2,SZ/2,c,r))*1.5+sin(t/2)/2,1),-1)*PI

yikes!

One thing I notice is that dist() call - this is what sets each spinner at a different place in the math, based on its position on screen. That's pretty clever! 

Ok, so to get a grip on this, I simplify the r + c counters so I just see a single spinner:
  for(c=100;c<101;c+=31){
    for(r=100;r<101;r+=31){
this will make it easier to see the patterns in side the massive b equation.  Even just watching one alone, it has a kind of hypnotic chaos to it. 

So every draw() it is createCavas()ing, I move that to a separate setup() so it just happens once, then I actually plot *one* of the values of B for my single spinner, sort of like a cardiogram:
if(i == 0) {
  stroke(128); rect(t*6,SZ/2 + b*10,2,2); 
  stroke(200); rect(t*6,SZ/2 ,2,2); 
}
that looks pretty cool actually:

So that's informative! We see how B gets clamped at 1 and -1 - but sometimes that's at the top of the arch, and sometimes it's at the bottom

I see that one part of the equation is really the heart, everything else is min/max. So I isolate it and call it minguts:
const minguts = sin(t+dist(SZ/2,SZ/2,c,r))*1.5 + sin(t/2)/2;  
then the equation for b is much simpler:
b=i*PI/2+max(min(minguts,1),-1)*PI;
Using a variation of the "cariogram" above, I plot out minguts in red, then min(minguts,1) in green then max(min(minguts,1),-1) in blue:
So the blue line is basically what we get for b, it's the end result. And red, the minguts... looking at its euation we see it's just adding two cycles of sin waves - the larger one influenced by that dist() stuff, the smaller one changing more slowly (t/2) and having less of an effect (the /2). Then the min and the max is just putting a ceiling and a floor to it- when it's at 1 or -1, I'm betting the spinner is straight one way or the other, and all the other values represent some level of spin.

If you want to see my "testbed" I hacked apart- kind of like dissecting a frog - it's here

So wow. I am in awe of this! It uses so much spatial smarts to make something really visually appealing. (I've seen that "spinners move to form negative space circles with their neighbors around) 

You can see more of Andy Makes tweet length code examples or more of his art and games on his website.

what color shall we paint the bike shed

 Today at work I had reason to mention bikeshedding, where too much time and attention is paid to not-significant-enough details. Here's another example of it used in context.

Saturday, September 4, 2021

links in the traditional blue

 Why is the sky are links on the web traditionally blue? A really fun look at very early browsers.

Fun ("fun") fact - I've had a personal start page ("kirkzilla", a play on mozilla) on line for, like, decades. At first it used a trick in early Netscape of displaying your saved bookmarks as a page, but later I made it its own thing. But I kept the colors the classic Mozilla even when I had to use CSS to do so...



Thursday, August 26, 2021

storybook - display the literal code in "show code" story block

 We were having a problem where the "show code" button for some of our components in storybook was being over-interpreted; like our formik config object was just showing as [Object object] and else where we were seeing children elements showing up as <MDXCreateElement> instead of the actual child.

One work around is to make sure you have docs.source.type = 'code';

Like in the meta of our .mdx that might be 

<Meta
  title='Components/Form'
  component={Form}
  docs: {
    source: {
      type: 'code'
    }
  }
  }}
/>

or we could do it globally in our preview.tsx, where we had a 

export const parameters = { docs: } 

All ready to go.

The downside is, this makes it so the "show code" content is no longer dynamic; we aren't applying it globally because we like the idea of having the source / properties for the current set of parameters shown. (like the code for a disabled button or whatever)

the music of the hemispheres

I slowly worked my way through Ian McGilchrist's The Master and His Emissary, his longest study into the division of focus of the left and right hemisphere of the brain and its possible implications for how societies construct themselves and evolve over time. 

Even if you are skeptical of the importance of the physical division in how our minds come together (though I think McGilchrist makes an excellent and well-researched case - I mean like 1/5 of the book is citations!) I think it's clear that there are several spectrums. In McGilchrist's view, the left hemisphere - the one that guides the right side of the body - tends to be very concerned with what it can control. It is reductionistic, it likes to categorize, it tends to isolate. In comparison, the right hemisphere is takes in the world as it is. It is holistic, everything is context-based.

Computer programming is intensely reductionistic. You solve a problem by breaking into tiny pieces. When explaining it to non-programmers, I tell it's like breaking a recipe into steps for an idiot chef. 

If you really push this idea, you get explanations for things like functional programming - the usefulness of completely isolating a bit of computation from context. Similar for unit tests: an extremely fine-grained way of studying every little pieces-part in isolation. 

McGilchrist worries that the left hemisphere (reductionism) tends to swamp the right hemisphere culturally, over time (with some counter-swings of the pendulum) but he doesn't think that the right hemisphere should predominate; the most fruitful times are when there's a balance. 

I see the for functional programming. On the one hand, it's easy to understand how code works best if you have clearly defined contracts and a clean room environment each time. On the other hand... it's often tough to get stuff done that way! I think of James Hague's essays on recreating retrogames as pure functional programs - if you think about all the state in even a humble game like Pac-Man: all the dots, the scores, the monster positions, etc - you REALLY have to jump through hoops to avoid terrible anti-patterns (whole state in, whole state out) and I begin to suspect it's would never have a good effort/reward ratio for forcing these games into the modality.

Similarly for unit tests. In my experience, programming bugs are an emergent property! In my experience, very few bugs are because of a blatant mistake inside of a unit... rather they show up as different units are drawn together, and these bugs reflect misunderstandings of contract or context. In other words, yes you want to keep an eye on your trees, but doing so obsessively isn't going to assure you have a valid forest. Higher levels of testing are critical, and to the extent resources put into testing one level might take from testing at another level, I would prefer the higher level views (that will, in turn, exercise the code at the lower levels.) But - it's a balance. (But my relative disinterest in unit tests is a rather unpopular view to people whose intuition believe it's THE key to keeping projects under control.) 



Monday, August 23, 2021

clickbait title images vs the skim

Netflix seems to be using more of a "clickbait" model. The article mentions one possible different path: analytics wise they ARE more closely tracking user engagement for like, entire series and 80% of full show, vs their previous "at least 2 minutes of watching".

I'll mention another factor that might ameliorate their clickbait trend: on most of their apps, their skim feature is really good. Empirically it looks as if they load a single giant image of thumbnails early-on, and once that is loaded you can skim the whole move -- effectively watching the whole thing on 10 or 20x fast forward, so if you ARE looking for something in particular: steamy scenes, battle scenes, whatever - you can ascertain that pretty quickly.

Sunday, August 22, 2021

homestar runner and the power of flash

Really good video praising both Homestar Runner and Flash as a medium in particular.

Flash's ignominious end is a tragedy! I never quite understood what happened. The reasons usually given for its demise are
1. "it's insecure". Like with Java applets, for some reason companies just couldn't get their acts together to batten down the hatches? Which is really weird, because it seems like stuff it would be very easy isolate in a virtual sandbox, especially relative to the trend to make javascript do everything.

2. "html5 is much better for video". I mean this is true, but as the video shows being "a wrapper for live action video" was a johnny-come-lately trick for flash

3. "it's hard on processor/battery life". I mean, I guess. But even in Steve Jobs' Thoughts on Flash letter, he's talking about video codecs, not the lightweight interactive animation that made Flash so powerful.

I think it's right to blame Apple for much of this. In that Jobs letter "Oh it's all mouse-centric, with rollovers, and our touch devices don't do 'hover'" is pathetically short-sighted - but it gets into the real reason, control of content. I think it's less "these Flash apps won't be good enough" and more they'd be too good.

Eventually, html5 technically picked up most of the capabilities that were lost when Flash went away, but there just isn't the community built up around it.

I feel bad that I didn't get into Flash... "Processing" was good enough for most of my stuff I wanted to build and then share via web, and now P5 plays that role. I took a one day class in Flash but it mostly dealt with animations that were easier to teach, not the type of interactive game stuff that I found most interesting.

I'm not sure where the action is, in terms of interactive content creation but especially community. P5/Processing has a little of that, "Scratch" probably has more - and Minecraft and Roblox, I think? And "Mario Maker". But much of the energy has been sapped off my either content creation community for non-interactive stuff, like Tik Tok.

In a related note I started playing around with Nintendo's "Game Builder Garage", pretty cool stuff as well... but it's always challenging to use a system where I feel a bit hobbled, especially at the beginning, relative to what I can make using traditional programming.

Tuesday, August 17, 2021

prokopetz on the tribulations of user-focused design

 While some people assume tumblr is a ghost-town, it actually is pretty vibrant, but is even more dependent on figuring out whom to follow than most social media. 

One of my favorite posters is prokopetz. He mostly talks about tabletop/roleplaying and games and general geek culture  (sometimes spinning off in hilarious ways with remarkable inversions) but I appreciated this take on user interface:

I guess a lot of my skepticism regarding user-focused design stems from working in tech support and witnessing first hand that user interfaces can only guide people to perform a task in a particular way if the method you have in mind is already very close to their prior assumptions about how the task ought to be performed.

If the way your UI thinks the task ought to be carried out and the way the user thinks the task ought to be carried out are very different, nine times out of ten what happens is that the user will Rube Goldberg together some bizarre workaround that’s ten times as complicated, takes ten times as long, and fights against the interface every step of the way, but preserves their prior assumptions, then become emotionally committed to their workaround and respond to any effort to demonstrate a more straightforward approach with extreme and disproportionate anger.

Critically, there doesn’t seem to be any way to avoid this scenario using pure design, because different people will have mutually exclusive prior assumptions about how the task ought to be performed, and you can’t possibly accommodate all of them. Conscientious design practices can supplement explaining shit in words, but they can’t replace it.

Figuring out the user’s possible mental model and explicitly hinting where it needs to be updated is so important! I’m a little more optimistic than the author, but still…

Monday, August 16, 2021

assembly

But as I read more about the physics of chips, I started to have a kind of acceptance of assembly language. I stopped seeing it as an annoying, unfinished abstraction--a bad programming language--and started seeing it for what it is: an interface to the physical world.
I am very bad at it now but I'm glad I dug into a little assembly language to make my Atari 2600 game JoustPong - and what I've learned about how damn physical and timer-based EVERYTHING is on that thing.

I remember having just a dash of assembly (of the Sparc or Solaris or whatever variety) in a computer languages class in college. It felt a bit dishonest, to be frank - like to run assembly that was talking about low level stuff like registers and what not, but then knowing it was on a system that was a bit virtualized, how it might be paged out by the OS at any time.

Saturday, August 14, 2021

believe it or not, i don't want the world to revolve around me

My sweetheart Melissa thinks it's bizarre that I switch my GPS app so that "north is always up", rather than the more common "forward is always up", with the display always shifting/rotation so it corresponds well with the view in front of the car hood.

There are three reasons I do this, in decreasing levels of silliness:

1. To up my manliness! I'm sure it's an outdated belief, but one nugget that stuck with my from like Cognitive Science 101 in college is allegedly men are better than women at performing mental spatial rotations. I don't want to get into the discourse here - I don't know if it's still a well-regarded finding, but it's a concept that stuck with me, and - ridiculously - I put GPS with "north is up" in order to get myself better at that kind of rotation. And thus, become "more manly" (snicker). 

2. To better reflect my philosophy of objective, shared reality over subjective truth. When I first got a GPS that rotated the view by default, I zoomed  way out, and realized how strange it was that, even though I'm on the east coast of the USA, the Atlantic was a big area on the LEFT of the screen, the opposite of what it is on a map. Regardless of what my friends think, I actually don't like the idea of the world rotating around me... the world - and by extension reality-  is what it is regardless of where I am at, and I kind of like my GPS to reflect that.

3. To better learn the lay of the land. I can't find the article, but a while back I read how there are two classes of tools - ones, such as training wheels or a temporary crutch, that build you up and let you perform better when you don't have them, vs ones you grow to depend on and so are made more helpless with their absence. An abacus is said to be in the first class - skilled users are greatly improved in visualizing mathematical operations even when without their preferred tool handy, while calculator users are more likely to be stuck. (I like that old gag, forget my Google search history, what would really be embarrassing is a usage history kept by a calculator app.) 

In fact that was a knock against the GPS, that would inhibit actually learning the geography. My response to that as an early adopter (In 2004 I got a Garmin StreetPilot 2610, $700 from walmart.com) was "I got news for you pal - I was bad at navigation even BEFORE I got the GPS, I was just more stressed about it"

But still I hope to reclaim some actual learning by keeping north as up, to continually work on building up my intrinsic feel for the way streets and neighborhoods are set out and are connected, to have to do a little mental work rather than being spoon fed with the map spun around me. 

(Melissa also hatest hates that I tend to mute the app so that the voice doesn't constantly interrupt my  music - especially when reciting long traffic sign-ish route identifiers for the second or third time. But that's a different story. I do wonder though, why Google Maps insists on breaking the silence when I cross a state border? Does it think I might be up to something nefarious and might need to know?)

javascript frameworks showdown

Pretty decent video building "TODO" in 10 different toolkits, starting with my favorite "vanilla" JS...

I thought he gave vanilla short shrift, really. For me it's a bit of a disservice to talk about the code alone, and ignore the fact you can just copy and work on code anywhere - and without pulling down 50Mb of random libraries "just in case". (I'm reminded of how BASIC was offered as an interpreted language on slow, slow early 8-bit computers - having absolutely minimal friction in the code/run loops is sometimes worth quite a bit) Also, using `` quotes to put HTML bits in works quite well as it does in some of the other frameworks.

Maybe I need to look into Svelte. I also liked Vue which I learned a bit to apply for some jobs last year. But mostly I need to give Preact a try.

Thursday, August 12, 2021

old dogs, new tricks: css grid for block schedules (vs absolute positioning and/or flexbox)

Usual disclaimer: In some ways I'm not a great tech blogger because my writing voice is more "well this is what worked for me" and sometimes even "except for this bit which I couldn't quite get where I wanted it." So definitely a bit of seeing how the sausages are made today.

Anyway. 2021's delayed Porchfest season (after 2020's year of cancellations) is approaching - albeit with a little more caution thanks to the Delta variant of COVID.

JP Porchfest, the site that got me starting all my endeavours on porchfest websites, is being run by a new group. One innovation they had (partially as a form of crowd control) is to have the event across both days of the weekend.

You never quite realize how assumptions baked into a system - e.g. "this event is just a one day stretch of time" - until they are challenged and you have to fiddle with code all over the place.

So I've had to scramble and update a lot of stuff. In some cases I've been able to fix some things that were bothering me. (I started doing these websites in 2014, with several generations of code, but sometimes 2014-era technique slips through and calls out to be replaced.) 

The block schedule has always been a challenging thing to make - in many ways having blocks hold positions on a page controlled by times and not their own natural flow goes against the way HTML does layout. Historically I  would do with a big heap of position: absolute divs. My first pass at updating the page to be two-day aware looked like this:


You can see the problem this approach always had, which is that with fixed height rows, performers with long names just didn't fit! Also my approach meant that porches were displayed even if there weren't any performances on that day. To fix both, I would have to go back and adjust the heights and positions of things manually (Earlier I worked up some nested DOM structures so I could read the computed heights) but since changes of heights in earlier rows effects positions of all lower rows... that is ugly capital Ugh!

(I think in previous years I also messed with techniques to experimentally shrink the font-size of any given entry so the name would fit in whatever box it was given... again, super hack-y even by my standards)

I had an idea to try to use flexbox but while that neatly solved the problem of having rows take up their natural height based on the content, I couldn't get the arbitrary positioning needed to represent a performances placement in time without manually setting each row item's height to the tallest object, thus somewhat defeating the purpose!

So my attention turned to css-grid, which is - kind of - a natural candidate for this sort of thing. I renewed my knowledge via CSS Grid Garden. It took a lot of work to rebuild the page, but my first pass at a usable result came out very promisingly:


Since different Porchfests have different run times I have to set up the grid dynamically, something like 

holder.style.gridTemplateColumns = `30px 200px repeat(${MINUTECOUNT}, 1fr)`;

Which holds space for the porchnumber, the porchname, and the breaks the rest up by however many minutes we have.

You can see it's a much more efficient use of space - especially since it's a lot easier to leave out porches that aren't hosting that day. I still don't have a perfect answer for performers like "DonYoyo" doing a 15 minute set. Also things got a little hacky as I put in the dotted line time bars showing each hour... but I can't let perfectionism stop me from getting the rest of the site done. I may swing back to figure out if I was doing something wrong or if there are just some quirks I have to live with (the "1/-1" for the rows of the separators just wasn't quite making them reach all the way to the bottom)

CSS Grid syntax feels very weird to me, like it's drawing from a print tradition or something... like how everything counts from 1 not 0, and you specify what column a thing stops short of at rather than the width or even which is the last column it occupies (so to take up the first 3 columns of 5 column layout it would be the shortcut "grid-column: 1/4", which is also weird because it looks like a fraction!)

Oh also, it's amazing how much more eloquent javascript is now that is ` ${variables} in template strings` and various map/filter/reduce functions, and fat-arrow functions. 

Thursday, August 5, 2021

p5 drawing clipping masks

I have an idea for an art project, roughly speaking using photos as wedges in a pie chart.

I knew you could make "masks" in p5 but wasn't sure if they had to be images... nope, stackoverflow says they can also be offscreen graphics. You make an offscreen image, draw the shape of the photo you want to see in black, and then apply it to your image object.

A slight gotcha - besides the slight oddness that the mask is applied to the image itself - is that the mask stretches to cover the whole image.

So this code:

let circleMask;
let myImage;

function setup() {
  createCanvas(526  ,688);
  circleMask = createGraphics(1000, 1000);
  myImage = loadImage('flamingo.jpg');
}

function draw() {
  background(100,0,0);
  circleMask.fill('rgba(0, 0, 0, 1)');
  circleMask.circle(500, 500, 500);
  myImage.mask(circleMask);
  image(myImage,0,0);
}

leads to 

i.e. even though the mask was made as a circle, when I applied it to the tall image, it got stretched into an oval.


Monday, August 2, 2021

ui gripe blog / hall of shame: Mass General Brigham Patient Surveys Online

 So my health care provider asked me to do a patient information survey, and offered this URL if I preferred to take it online:

https://massgeneralbrighampatientsurvey.intelliscaninc-surveys.com/english

That's quite a lot to type! Plus, if you eschew the https:// and make a typo in the first part of the URL - you get taken to a 404 page. But then correcting the typo in that ~30 character URL section doesn't fix anything, because you're stuck in "http" land, not https...

So

A. that is a horrible URL to expect a human to type

B. 404 pages should be smart enough to try to bounce people to https.

Saturday, July 31, 2021

ui gripe blog: not preserving zoom across modes in iphone's photo app

All too common thing for me: see a possible candidate for my "One Second Everyday" project, fire up the camera app, zoom-in, notice I'm on photo, switch to video-- but the zoom resets and I have to zoom-in again. 

A more mature UI would recognize "just zoomed in/out, then switched modes" as a request to say "show me this same shot but in the different mode". There are some technical gotchas across modes but still.

Sometimes I think I'm the only person in the world who cares about UX ;-) 

Thursday, July 29, 2021

moving common sibling nieces leaf properties up to be... siblings? in a map

 So this is almost harder to describe than show, but we have a big map like object. CSS properties for components are at the leaves, so for instance "button.square.lg.pressed" might have the CSS key/values for the pressed state. But that leads to a lot of duplication, like maybe "button.square.lg.idle" is the same except for the color or something. So we'd like to move the actual properties up to point where every child has them in common, so 

{
 "button": {
  "idle": {
   "margin": "16px 8px",
   "backgroundColor": "white"
  },
  "hover": {
   "margin": "16px 8px",
   "backgroundColor": "lightblue"
  },
  "focus": {
   "margin": "16px 8px",
   "backgroundColor": "darkblue"
  }
 }
}

might become

{
 "button": {
  "idle": {
   "backgroundColor": "white"
  },
  "hover": {
   "backgroundColor": "lightblue"
  },
  "focus": {
   "backgroundColor": "darkblue"
  }
 },
 "margin": "16px 8px"
}

like that.
A coworker basically did the work (using lodash's isObject and isEqual) - here is a codepen using lodash and typescript, the code is 

// import { isObject, isEqual } from '/lodash';

const hasPlainChildren = (obj: any) => {
  for (const key of Object.keys(obj)) {
    const child = obj[key] as any;
    if (!_.isObject(child)) continue;
    for (const childKey of Object.keys(child)) {
      const newChild = (child as any)[childKey];
      if (_.isObject(newChild)) return false;
    }
  }
  return true;
};

const promoteValues = (obj: any, ignoreList: string[]) => {
  const newObj = { ...obj };
  const items = [] as { key: string; value: any; presentIn: string[] }[];
  if (!_.isObject(newObj)) {
    return newObj;
  }
  for (const key of Object.keys(newObj)) {
    const child = (newObj as any)[key];
    if (!_.isObject(child)) continue;
    for (const prop in child) {
      if (_.isObject((child as any)[prop])) continue;
      const valueInSet = items.find(
        (item) => item.key === prop && item.value === (child as any)[prop],
      );
      if (valueInSet) {
        valueInSet.presentIn.push(key);
      } else {
        items.push({
          key: prop,
          value: (child as any)[prop],
          presentIn: [key],
        });
      }
    }
  }
  for (const item of items.filter((item) =>
    _.isEqual(
      item.presentIn,
      Object.keys(newObj).filter((key) => !ignoreList.includes(key)),
    ),
  )) {
    (newObj as any)[item.key] = item.value;
    for (const key of Object.keys(newObj)) {
      const child = (newObj as any)[key] as any;
      if (_.isObject(child)) {
        (child as any)[item.key] = undefined;
      }
    }
  }
  return newObj;
};

export const simplifyObject = (obj: object, ignoreList: string[]) => {
  let newObject = { ...obj } as any;
  if (!hasPlainChildren(newObject)) {
    for (const key of Object.keys(newObject)) {
      if (!_.isObject(newObject[key])) continue;
      newObject[key] = simplifyObject(newObject[key], ignoreList);
    }
  }
  newObject = promoteValues(newObject, ignoreList);
  return newObject;
};
const foo = {
  button: {
    idle: {
      margin: "16px 8px",
      backgroundColor: "white"
    },
    hover: {
    margin: "16px 8px",
    backgroundColor: "lightblue"
  },
  focus: {
    margin: "16px 8px",
    backgroundColor: "darkblue"
  }
}
};

window.time.innerHTML = Date.now(); 
window.before.innerHTML = JSON.stringify(foo, null, ' ');
window.after.innerHTML = JSON.stringify(simplifyObject(foo,[]), null, ' ');

with this bit of HTML to show results:

<pre id="time"></pre>
before:
<pre id="before"></pre>
after:
<pre id="after"></pre>

Haven't really poured over the code but it seems to do the job!

Wednesday, July 28, 2021

WiP: showing state combinations of components

This is a work in progress, and is probably the wrong idea in general (see earlier and later posts) but I'm kind of proud of the concept...

One thing I am adding to my team is the idea of making things visible, so that dev testing and QA is easier, seeing things work in context. (For example I made a "formik playground" page in storybook so we could see our components work in formik context, still work out of that context, and then how formik "regular" fields would work so we could compare behaviors etc)

Related to that, my team has realized we sort of painted outselves in a corner in terms of not thinking about different possible states of components (error, disabled, selected, etc) function together. Most of these are "boolean" properties so I thought a simple grid could let us see all the two-fold combinations, how the states play together:

The end result looked like:



And the code (not quite perfected, especially from the typescripting complaints side:)


import React from 'react';
import PropTestingProps from './prop-testing.props';
import PropTestingComponent from './prop-testing.style';
import { Button } from '../Button/button.component';

import { Checkbox } from '../Checkbox/checkbox.component';
import { Radio } from '../Radio/radio.component';
import styled from 'styled-components';

const Table = styled.table`
  th {
    font-weight: bold;
  }
  th, td {
    padding: 8px;
    font-family: sans-serif;
  }
`;

interface ComponentPropMatrixProps {
  label: string,
  permutations: string[],
  render: React.FC
}
interface ComponentPropRowProps extends ComponentPropMatrixProps {
  rowEntry: string,
  permutations: string[],
  render: React.FC
}

const ComponentPropRow = (props: ComponentPropRowProps) => {
  const {permutations, rowEntry, render} = props;
  
  return (<tr>{permutations.map((colEntry: string)=>{
    const componentProps = {[colEntry] : true, [rowEntry]: true };
    return (<td key={colEntry}>
      {render({componentProps,label:`${rowEntry}`})}
        </td>);
  })}</tr>);
}

const ComponentPropMatrix = (props: ComponentPropMatrixProps) => {
  const {permutations, render, label} = props;
  return <>
    <h3>{label}</h3>
    <Table>
    <thead>
      <tr>
        {permutations.map(x => <th><b>{x} +</b></th>)}
      </tr>
    </thead>
    <tbody>
      {permutations.map((x,i) => {return <ComponentPropRow key={x} 
          render={render} permutations={permutations} rowEntry={x}/>})}
    </tbody>
    </Table>
    </>

}
// idle, checked, error, disabled

const PropTesting = (props: PropTestingProps) => {
  return <PropTestingComponent>
    <ComponentPropMatrix label="Checkbox" 
      permutations={['idle', 'checked','error','disabled']} 
      render={({componentProps,label})=><Checkbox {...componentProps} 
        label={label} />} />

    <ComponentPropMatrix label="Radio" 
       permutations={['idle', 'checked','error','disabled']} 
      render={({componentProps,label})=><Radio {...componentProps} 
         id={`${JSON.stringify(componentProps)}!`} label={label} />} />


    <ComponentPropMatrix label="Button sm primary" 
      permutations={[`'idle'`,'disabled']} 
      render={({componentProps,label})=><Button size="sm" 
      buttonType="primary" {...componentProps}>{label}</Button>} />
    <ComponentPropMatrix label="Button md tertiary" 
      permutations={[`'idle'`,'disabled']} 
      render={({componentProps,label})=><Button size="md" 
      buttonType="tertiary" {...componentProps}>{label}</Button>} />
    <ComponentPropMatrix label="Button lg oval" 
      permutations={[`'idle'`,'disabled']} 
      render={({componentProps,label})=><Button size="lg"  
      shape="oval" {...componentProps}>{label}</Button>} />

  </PropTestingComponent>;
};

export default PropTesting;

javascript get all subsets of an array, then sort subsets by length, and preserve order

This stackoverflow  had an elegant all permutations generator:

const getAllSubsets = 
      theArray => theArray.reduce(
        (subsets, value) => subsets.concat(
         subsets.map(set => [value,...set])
        ),
        [[]]
      );
const elems = [1,2,3];
console.log(JSON.stringify(getAllSubsets(elems)));

I can't say I really understand how it works! But here is the output:

[[],[1],[2],[2,1],[3],[3,1],[3,2],[3,2,1]]

I realized I would like it better if we sorted by length... the empty set, each single entry set, each 2 entry set... 

console.log(JSON.stringify(
      getAllSubsets(elems)
          .sort((a,b)=>a.length - b.length)
));

That made

[[],[1],[2],[3],[2,1],[3,1],[3,2],[3,2,1]]

Much better! But still, it would be cool if each individual array was sorted - not on the value but based on the order in the original set:

So I end up making

console.log(JSON.stringify(
      getAllSubsets(elems)
          .sort((a,b)=>a.length - b.length)
          .map(arr=>arr.sort((a,b)=>elems.indexOf(a) - elems.indexOf(b)  ))
));

That's cool! let me change elems to ["Foo", "Bar", "Baz"]:

[[],['Foo'],['Bar'],['Baz'],['Foo','Bar'],['Foo','Baz'],['Bar','Baz'],['Foo','Bar','Baz']]

Perfect!

UPDATE: the Typescript version that function is

const getAllSubsets = (theArray:string[]) => theArray.reduce(
        (subsets, value) => subsets.concat(
          subsets.map(set => [value,...set])
        ), [[] as string[]]
);

Monday, July 26, 2021

the ux of preconceptions

Besides Facebook (which, despite its many flaws, does the best job of keeping me in touch with people I know in real life but not well enough to be in touch by other means) my favorite form of social media is tumblr. 

When I mention that to people, the usual response I get is "is that still around?" which is kind of sad. Splitting the difference between (old) LiveJournal's long-form blogging and Instagram's reliance on images, it has a solid socially conscious community. (Like Twitter, it's really important who you follow, and unlike Twitter, you mostly just see the people you follow, in the order they post things.)

Anyway, one of my favorite tumblr sources is David J Prokopetz. Mostly I love his extensive riffs on tabletop games but also I thought this was a pretty good set of thoughts on UX in terms of user's preconceived mental models:

I guess a lot of my skepticism regarding user-focused design stems from working in tech support and witnessing first hand that user interfaces can only guide people to perform a task in a particular way if the method you have in mind is already very close to their prior assumptions about how the task ought to be performed.

If the way your UI thinks the task ought to be carried out and the way the user thinks the task ought to be carried out are very different, nine times out of ten what happens is that the user will Rube Goldberg together some bizarre workaround that’s ten times as complicated, takes ten times as long, and fights against the interface every step of the way, but preserves their prior assumptions, then become emotionally committed to their workaround and respond to any effort to demonstrate a more straightforward approach with extreme and disproportionate anger.

Critically, there doesn’t seem to be any way to avoid this scenario using pure design, because different people will have mutually exclusive prior assumptions about how the task ought to be performed, and you can’t possibly accommodate all of them. Conscientious design practices can supplement explaining shit in words, but they can’t replace it.

Good stuff!

Saturday, July 24, 2021

different mediums for better messages

Thinking on "body neutrality" (as a healthier and more realistic alternative to "body positivity") reminded me of a quip I made years ago - I've been trying to note when I remember early, nascent forms of my current philosophical stances - and I looked it up in my blog:



One of my favorite tags on my blog is /tag/aim, (mostly) bits from the old AOL Instant Messenger days. For a while I assumed it was mostly nostalgia that made me think "damn, we were funnier then" (or maybe just being a bit younger and more quick-witted after all!) but you know? The modern "equivalents" of AIM - SMS/WhatsApp etc... most of them are phone based. And it's much more challenging to get banter going between people tapping into their screens than with two competent typists!

Thursday, July 22, 2021

simplenote lifehack: "findme" as searchable string for important notes

 For at least 7 or 8 years I've been using (and increasingly relying on) "Simplenote", a hyper-minimalist text only note taking app that is EXTREMELY good at synching to my desktop and iOS app (and has a webclient as well.)

Simplenote doesn't offer folders. I've grown to love its "just a big pile of notes" approach - and to not be overly worried about separating the what from the chaff. There is a "tag" system, but I find it a bit cumbersome. Also for a while I would "pin" the important notes to the top, but that grew unwieldy as well.

I think I've found a great solution: for important notes I add "FINDME" to the title. This integrates much more smoothly with the speedy search bar than the tag based approached, letting me pluck out the relevant note from all the others with similar content just by typing.

Tuesday, July 20, 2021

formik/yup problem: validating wrong value

 Odd geeky problem from work, we have components that support Formik - specifically if they find themselves in a FormikContext they take their values from there.

So one thing that's easy to miss if you're not paying attention is Formik components probably shouldn't display their error message unless they have been "touched" (submitting the form counts as a "touch" for each element. And of course, if you are making your own components, they should set "touched" once they are interacted with - the easiest way is to use the FormikContext.handleBlur(event), but some of our made up components - specifically some radio buttons - didn't have the "event" needed (a non-input DOM element was handling the click event), so I had to call FormikContext.setFieldTouched() directly. 

But we were getting weird results... we had Yup have the field as required, and even though a radio value was selected the error message for Required was still showing up.

We'd already built a "Formik Peek" component:

const FormikPeek = () =&gt; {
  const formikContext = useContext(FormikContext);
  const valuesString = formikContext &amp;&amp; JSON.stringify(formikContext.values);
  const errorsString = formikContext &amp;&amp; JSON.stringify(formikContext.errors);
  const touchedString = formikContext &amp;&amp; JSON.stringify(formikContext.touched);
  return (
    <table>
      <tbody>
        <tr>
          <th>values</th>
          <td>{valuesString}</td>
        </tr>
        <tr>
          <th>errors</th>
          <td>{errorsString}</td>
        </tr>
        <tr>
          <th>touched</th>
          <td>{touchedString}</td>
        </tr>
      </tbody>
    </table>
  );
};

And that was showing that the value was being updated ok, but Yup was being run.

So this is what we had as our onChange equivalent:

  const localOnChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
    onChange && onChange(event);
    formikContext && formikContext.setFieldValue(componentName, value);
    formikContext && formikContext.setFieldTouched(componentName, true, true);
  };

So, the trouble was the second "true" to setFieldTouch, which says "validate this now". That was somehow being read as "validate with the old value"- maybe because the setFieldValue and setFieldTouched was in the same thread? I dunno. But even with validation set to false, it still seemed to validate at about the right time, so I'm not sure when "true" is useful. 

Monday, July 19, 2021

JSON prettifier

 Oh this is cool - FracturedJSON gives you a more reasonable layout for your JSON:


JSON.stringify(value, null, ' '); tends to spread array contents across multiple lines - a bit better than your whole structure as one big old string, but not very efficient to skim.

(Via Javascript Weekly which I recommend for everyone doing Javascript)

Thursday, July 8, 2021

the potential trauma of "on this day"

Lauren Goode on I Called Off My Wedding. The Internet Will Never Forget - now that the Internet is a memory-aid for so many of us - and specifically, via tools on social media that has its own agenda for keeping people engaged - the potential for opening up old wounds is huge.

A lot of the topic is "On This Day", which is a common way to review old times when using a digital memory keeper. I've had a This Day in Kirkstory feature on my website for a long time. (Most of my memories, even the potential painful ones, have the analgesic of nostalgia.)

Thinking about it, the personal "On This Date" feature is kind of new and weird! I guess birthdays have always had a bit of it, and newspapers have long had "this date in history" features, but when used as a larger cross-section of life, the arbitrary nature of it emerges. It really leans into the cyclical / seasonal nature of life. Why should July 8ths across a life otherwise be seen as having stuff in common?

(Interestingly, Apple's "Memories" feature doesn't seem stuck in that rut. It keeps its cards pretty close to its chest in how it decides to curate things.) 

Tuesday, July 6, 2021

making space invaders modern

Bringing (space-invaders) emulation into the 21st century: Implementing an 8080 emulator in a microservice architecture on top of kubernetes 

This kind of abstraction overkill reminds me of the over-engineering of programming Pac-Man in a pure functional kind of way.

Sunday, June 27, 2021

pell, granted: minimalist javascript install of a minimalist wysiwyg html editor

For obvious reasons 2020 was a pretty quiet year for Porchfest, and this year I am playing catch up on the website stuff, blowing off all the dust and continuing the effort to get every porchfest site running on the same tech base.

An underbaked feature has been the ability for admins to edit various snippets on the site. Few admins are fluent in HTML, so my crude "edit the raw source code" was less than ideal. 

I browsed around for various options. The one I landed on was Pell, a super-minimalist but still pretty well-featured editor. That page has the demo, the github page has some installation instructions, but it's kind of a mix of "run it from <script src> tags and code that looks like it's assuming there's going to be a build process.

Here's what I ended up with, my final Proof of Concept before integrating the tech into the Porchfest site:

<head>  
  <link rel="stylesheet" type="text/css"
     href="https://unpkg.com/pell/dist/pell.min.css">
  <style>
    .pell-content {
      background-color: #eee;
    }
  </style>
</head>
<body>
  <div id="editor" class="pell"></div>

<form action="/form/" method="POST">
    <input name="content" type="hidden" id="content" />
    <button>Save Changes</button>
</form>
  <script src="https://unpkg.com/pell"></script>
<script> window.pell.init({ element: document.getElementById('editor'), onChange: html => document.getElementById('content').value = html }); const start = '<b><u><i>Initial content!</i></u></b>'; document.getElementById('editor').content.innerHTML = start; document.getElementById('content').value = start; </script> </body>

So, I just tuck the script stuff at the end, after the page is loaded, I seed the editor and a hidden input field with the content, and then the onChange keeps the hidden input field up to date, and then I can submit that to a form.

I have to find out how tolerant the editor is of crappy HTML that might have been in the system before, but this is really promising!

https://github.com/jaredreich/pell also has a list of similar editors, comparing size and what not)