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)

Friday, June 18, 2021

old man yells at (the) cloud

Or Web-Development is running into the wrong direction.

It does make one ponder the Rise and Fall of JQuery.

I think its main problem is that it never had a great solution for how to tie in buttons on their actions, and frankly pre fat-arrow and pre-good-scoping Javascript was a mess... anytime you had to think about "this" was a trainwreck.  And there wasn't a great way to import a module. 

And now, most of the standardization it provided is handled with more mature browsers and a lot of the syntactic sugar is moved into vanilla JS. But You May Not Need JQuery as a translation glossary demonstrates that many of the ideas it brought to the table were worth keeping.

Still, I think it's more a need to make front end programming feel more like back end programming is what stinks right now. "front end programmers won't feel respected and be happy til they have a build process that's as long as the back end" was my prophecy, and I think that's about right. 

making formik-powered components that still work without formik

On my team we are making a component library, and we decided to make our components Formik aware, so that when used in a Formik context they would populate themselves, handle the onChanges, detect and show errors (using Yup validation) etc. But we also wanted them to continue to work OUT of the Formik context. But when we did so, we'd see this error in the. console:

Warning: Formik context is undefined, please verify you are calling useFormikContext() as child of a <Formik> component.

We were grabbing the Context using

import { FormikContext, useFormikContext } from 'formik';
//...
const formikContext = useFormikContext();

Weirdly we get better results (i.e. no warnings) by using React's useContext instead:

import React, { useContext } from 'react';
import { FormikContext } from 'formik';
//...
const formikContext = useContext(FormikContext);

Not quite sure why that works, but it does.

(Another small Formik point - it might be more or less obvious from the examples, but you probably want to make sure you only display contents of formik.errors['thisValue'] if formik.touched['thisValue'] is true. However, the usual submit handling will set all touched values to be true. Which seems weird at first, but makes sense in practice - that's as touched as those components are going to get!)

Thursday, June 17, 2021

icon art circa 1994

For a brief time in the early 90s I was the "webmaster" for Tufts University's Comp Sci department. I hand drew some icons for the homepage - strong Keith Haring energy.



Tuesday, June 15, 2021

cute little apple ui feature: playlist thumbnails

I'm very set in my musical ways: I like purchasing single mp3s (or the equivalent), and then resorting to youtube rips if I have no other option. I've rated all my music ever since my iPod only had room for "3 stars and up", and then have good, better, best (3+ stars, 4+, and 5, respectively) smart playlists, and then try to listen to the "good" playlist (in reverse chronological order) daily, so I form a connection with the music. 

Anyway, Apple has a curious algorithm for assembling a thumbnail for a playlist, and mine has been consistent for a long while:



I think it's picking "A,B,C,D" so in my case, Ani "Buildings + Bridges" DiFranco, BPA ("Toe Jam" with David Byrne - fun sort of NSFW video for that), CAKE ("The Distance" was my senior solo), and Dar "Cool As I Am" Williams.


Monday, June 14, 2021

what makes Apple's M1 so good?

By all reports, Apple has done something miraculous with its M1 chip - like every chip maker is doing as good of a job as it can, and then Apple is like Homer as the new manager at the Bond-ish Villain's Nuclear Plant:


Here's a good article going into how the M1 stands out.

Its interesting that Apple's secret sauce is System-on-a-Chip, and intense specialization (I mean even the earliest iPhone blew other devices out of the water in terms of responsiveness, in part because of custom scrolling hardware. At the time I thought about the old Amiga and Atari ST, and how much more interesting game machines they were than the PC, it took the PC years to catch up with sound and graphic cards - its own custom hardware, but less well-integrated) But that specialization seems to be at cross-purposes to how Apple is using the same chip family for all devices: from Watches through Phones and iPads and Macs.

Cool times for Apple fans!

Friday, June 11, 2021

quality assured humor

A joke from Bad Jokes by Jeff:

A software tester walks into a bar

Orders a beer. Orders 0 beers. Orders 999999999 beers. Orders a bear. Orders -1 beers. Orders hdtseatfibkd.

First real customer walks into a bar and asks where the bathroom is. The bar bursts into flames killing everyone inside.

Wednesday, June 9, 2021

"contenteditable" div as replacement for textarea that stretches to fit content

 A while back I made this simple invoice maker and I decided I wanted to make the line items multiline. But it seems like it's weirdly hard to get a textarea to stretch to content! One suggestion from that page is to make a div with the "contenteditable" property... then you get the usual div niceness of stretching to fit content. 

I had to come up with a javascript hack to copy the div contents into hidden form variables onsubmit:

    function loadDescAsHidden(){

        window.desc1.value = window.desc1div.innerHTML;

        window.desc2.value = window.desc2div.innerHTML;

        window.desc3.value = window.desc3div.innerHTML;

    }

So not the prettiest, but good enough for my purposes.

Sunday, June 6, 2021

ui hall of shame: HBO Max on Samsung TVs

Daring Fireball linked to an article HBO Max tvOS Update Breaks Several Key Features, Degrading Experience Significantly. One thing I've noticed is that the HBO Max app on my Samsung TV has been weirdly, uniquely bad... mostly because on every other app, the play/pause button pauses. On HBO Max it brings up a tutorial page for a random command Samsung's Bixby voice command features:

For unfathomable reasons, the inner button on the circle control, which means "OK" or "Select" everywhere else, is what is used for pausing. The overall effect is as if HBO let a Junior Dev release a half baked experiment with Samsung's API out without any user testing. 

(FWIW, I think the UI on the Samsung TV is pretty decent... light years ahead of the older Samsung my folks have. I'm told that Smart TVs are so cheap in part because they are subsidized by selling viewing information to advertisers. I'm actually ok with that bargain, to be honest - I guess I run a little insensitive on privacy issues as long as it's treating me as J Random Viewer and not someone specific.)

Friday, June 4, 2021

obscure html tag attribute of the day: spellcheck="false"

 Huh! My screenshots for my Timesheet Tim "meme"-thingy were marred by red underlines for misspelled words (attempts to capture a Dickens-esque or faux-Cockney accent in writing, attempts that make Dick Van Dyke look pretty good) but I found out a 

spellcheck="false"

on the textarea works pretty well. I think maybe it can on a form tag as well, and be inherited?

Thursday, June 3, 2021

hiding out from the const cops

 So, some of my coworkers are much more fired up about insisting on the use of const for javascript variables than I am. I think it's a bit of a shame, especially because nothing stops you from doing:

const foo = { bar: 123};
foo.bar = 456;

There are different ways of freezing an object like that, but I think people who care about const think of it like a "Poor Man's Immutable". 

So I was surprised when code like this showed up in a Pull Request:

const someFunction = ( { bar } ) => {
    // some other work
    bar = 456;
}

I guess I assumed properties were const... but no. And in fact, there's not a clean syntax for it, that keeps up with the syntactic sugar.

On the WesBos slack domain, A-J Roos says:

You’d have to destructure from the arguments object if you don’t want the variables to be reassigned.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments

TypeScript has a readonly type that you can use to define function arguments that cannot be reassigned during development.

https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype

So, huh. 

Wednesday, May 26, 2021

applications and appliances that think they know

We recently got a condo and it came with a microwave that is much more powerful than our old one. One thing I find irksome about it is the message that comes up when the heating cycle is done:


"YOUR FOOD IS READY"

I hate the presumption, that the appliance knows what I'm doing! It just doesn't! Maybe it's food that has a multi-step form of preparation! Or maybe I'm heating up a beverage! Eh? Eh? Ever think of that you stupid microwave? That's why we say "food AND drink". Or maybe we're warming a heating pad or something! Yeah, you're convenient at heating up moisture-laden things but you're not the end-all be-all of food preparation, stay in your lane.

Similar deal for Google Maps. I felt it was a bit overbold to say "Welcome Home" when I arrived after plotting the landmark I labeled as "Home" - like, you're not my part of my household, dude. But worse is finishing a trip and the destination was a restaurant... like I think it says "Bon Appetit!" or whatever. What if I was just using the restaurant as a landmark? The cyber-chumminess is grating.

I know my gripes seem petty, but they're all a hallmark of more fundamental problems when a designer assume they understood the fullness of a usecase and then try to make convenience that only apply to that usecase, and mask the underlying details of the task. Like a smart appliance that "learns" from when you change the thermostat, but then of course has no idea to stop doing that when you're on vacation. Or AI-ish stuff that offers to make an appointment in your calendar based on the content of an email. The hit rate is pretty 50/50 in terms of it getting enough context to do it properly - and while I'm sort of lax about privacy I have mixed feelings about a corporate-backed AI smart enough to do it well reading all my mail.

I much prefer tools that focus on a single task, that don't assume they know what you want to do before you do, and that "show their work" so when they do misguess they are easily corrected, and if the user  wants to do something slightly off the well-beaten path they understand the underlying actual concepts, it's not just "computer always did it for me!" magic.  Systems that are all built on "Guess What I Mean" philosophy often make you pay for the extra convenience with later frustration.

Monday, May 24, 2021

unicode icon-like things

Here are 100+ unicode symbols that you can use instead of icons. Nice to be able to do some iconography but still keep everything as a "simple" string.

Friday, May 14, 2021

Timesheet Tim and the Ghosts of Timesheets Future

 My company has a moderately annoying timesheet policy. 

At a previous gig, I took on the form of Nagbot 3000 (those were status updates, actually).

I've been trying to help nudge my work gang - it's not like I really care (heck my manager things they're rather silly too, but you know- business) and when you google for Timesheet Reminders, most of the memes are kind of like "I want you to do timesheets":


But that belligerent vibe isn't what I'm going for! I mean, I don't care about timesheets, I just know that they're an easy thing to forget, and I want to help my teammates...

Anyway, last week I typo'd "timesheet tim!" for "timesheet time!" and it kinda sorta stuck so I think I'm just going to stick with this image from now on:

(I scribbled in my glasses and thin beard) I like the idea of a character who is just... enthusiastic about timesheets, and hope you will share in his joy.

Hm. Maybe I should make a meme-generator like thing for this, have him mix it up every week...

UPDATE: I made the generator. Didn't do any fancy P5 that would generate the image, screenshots should be enough I think.

rule of thirds redux

A while back I took a photography class, and of course was introduced to the "rule of thirds", using a "tic-tac-toe" board to help line up things in the shot. (Most cameras/apps have an option to show the thirds.) So I asked my instructor... "this rule of thirds thing... like sometimes we use it to put things in the squares:

Other times the point seems to be to place things on the separating lines or intersections:
So which is it?"

Her wishy-washy response taught me the lesson: mostly it's just a trick to stop newbie photographers from centering every damn shot.

Myth #4 of this video agrees with this observation:

pretty pretty macs

I really liked this graphic from  The New iMac Design is Perfect:


I really do like my 32" LG monitor which for me has the a similar energy, especially with the slight uptilt I have it at. The other day I documented my office/dressing room:


(very self-indulgent link going over all the art and artifacts) 

If you're fortunate enough to be able to make a "room of ones own" sanctuary/workspace, I'd recommend it!

Wednesday, May 12, 2021

simplest deep array copy in javascript

Whenever I go a while without a blog update, I think of the character "Jim Anchower" on the Onion, with editorials that usually started "Hola, amigos. I know it's been a long time since I rapped at ya".

But, you know. I moved last month, and work has been very good but more about building with tech we established in previous sprints than learning new stuff I wanted to share and record...

Anyway, I had to look up how to clone arrays in javascript... most of them are only shallow copies, bad for two dimensional stuff, but this trick works a treat:

const copiedArray = JSON.parse(JSON.stringify(originalArray));


Thursday, April 29, 2021

when reviewing code, don't be walter monheit, the movie publicist's friend

 I've adopted not being judgmental and always giving the benefit as kind of a life style, which means sometimes I have trouble doing thorough enough code review. Sometimes I feel like Walter MonheitTM, the Movie Publicist's Friend, in the much lamented Spy magazine: 


I do understand that's not the best stance to have while reviewing code, but it's tough to second guess other developers and assume I know more about the problem (or was more observant) than they were...

Wednesday, April 21, 2021

mac bias and the joy of giant screens

I have few illusions that I have huge masses waiting with bated breath for my next dev blog post, but I feel bad when a month only has a few sparse posts. My excuse is, my sweetie and I have been moving into a new condo! 

I haven't been doing much work on my current freetime project, the splash-o-matic 2600, but today I picked it up again. 

One feature I threw in: a mode where left mouse button draws and the right mouse button erases. I've seen that in other homebrew Atari editors, but it seemed kind of corny to me. But I've come around: for a system so based on boolean values for pixels, it makes sense, and I can see that it was my "but Macs only have one mouse button!" bias that influenced my thinking - even though I've been using a 2 button mouse ever since I rearranged my desktop and put my laptop on a stand. 

One problem was in chrome etc, right clicking would pull up a context menu - this stackoverflow had a pretty clean solution: (specific to p5, which adds a "p5Canvas" class to its canvases) 

function setup() {
  for (let element of document.getElementsByClassName("p5Canvas")) {
    element.addEventListener("contextmenu", (e) => e.preventDefault());
  }
}

Speaking of rearranging desktops - early on in COVID quarantine I got an extra monitor to use in vertical/portrait mode, along with a large-ish 28" Samsung monitor. But in the interest of a less cluttered desktop and few wires (and arguably less blatant geek "virtual signaling") I've replaced those two with a single 32" LG monitor - one in a close to "normal" ratio, not those Cinemascope-esque wide wide things. 

It's great - especially when combined with SizeUp that lets me use keyboard commands to throw windows to half or quarter the screen - it's like having 2 side-by-side portrait mode monitors. (I remember how proud I was of my "massive' geek-sized" CRT in college... must've been 19" - and each "half monitor" is a bit larger than that now. And it's really elegant looking, not quite as amazing in design as those new iMacs - like it's less colorful, but has the same tilt up pivot, and is larger and cheaper! 


Friday, April 16, 2021

Wednesday, April 7, 2021

old school mac command line search and replace

 Aargh! I was digging "Prettier" as a plugin for VS Code, but it has an irreconcilable difference opinion from my team on bullets in markdown  -- namely if - or * should start an unordered list item. Apparently this is an issue of some holywar, opinionated tool crap and I have already wasted enough time for it, so here is an old school command line thing that seems to fix it for me:

find . -name "*.mdx" -exec sed -i '' 's/^\- /\* /g' {} \;

That says, find every markdown file (*.mdx) and run the old sed command ... -i with '' says don't make a backup file, the regex is beginning of line and a dash and a space replaced with a star and a space, globally. 

Kind of sucks but easier than wrestling with Prettier config or eslint plugins right now.

spacejam.com, adieu

 For two and a half decades, spacejam.com looked like this:


Such a nice little slice of online life, near the dawn of the popular web.

Alas that page has now been shuttled off to www.spacejam.com/1996/ and presumably modern stuff about the new movie will fill the root of it.

It reminds me of webolution of web design that imagines a simple NASA page reimagined in the dominant web styles of the day (you can flip through eras via the slider at the bottom.)

Thursday, April 1, 2021

auto-contrast

I'm still toiling on the Atari 2600 graphics editor (with import) I mentioned the other day - the splash-o-matic One small bug (or at least poor UX) was that a dark imported image might come in as all black pixels, and so look like the process had failed. So besides adding a "loading" bit of text, I made a heuristic that tried 5 different contrast levels and then picked the one that had the closest to even split of black and white pixels. Works pretty well, and now an image is at least discernable, even if the user will still probably want to play with the contrast slider some more.

Now that I think about, maybe a better algorithm would have been to see what contrast settings had the most detail... maybe by counting "how many pixels are touching a pixel of a different color". Hmmm.

Tuesday, March 30, 2021

nativefier - native mac/windows/linux apps from a webpage

 Huh. nativefier is pretty cool and super easy to use to get an app wrapper for a webpage (haven't yet fooled around enough to see how it plays with Apple's usual "do you trust this developer" gatekeeping)

As I make my Atari Tools, the idea of long term support comes up, since some of these projects are one-person-shows, and sometimes folk lose interest, or something happens to them. That's one reason I try to keep my software all browserside, ideally with trivial build processes - it should be pretty easy for a coder to pick up my stuff if they wanted to.

Monday, March 29, 2021

import images to atari code, all in your browser - the new jersey approach "worse is better"

I'm sure I'll have more to say about the UI behind Splash-O-Matic 2600, my upcoming browser-based tool to make large, ready-to-run images for the Atari 2600, but right now I'm chuffed that I got an image "upload" feature working - except it all happens in the browser!

Here's the editor page:

and here it is running on an Atari Emulator - 

The trick is using the createFileInput() feature from P5. I didn't get the (probably more performant) loadPixels() version working, but get() let me read from the pixels and take a guess if this square should be black or white.

I was worried this was going to be super-tough to add to my tool, but it turned out to be pretty easy. The trick was not to overthink it: in theory I could let the tool carefully scale and crop etc the uploaded image - but that would really complicate the UI. I decided to let the program just scale the original image to the width and height of the canvas, then read the pixels. (In atari-pixel-size chunks) 

I've been thinking a lot lately about The Rise of Worse is Better. This is a good example of the "New Jersey approach" favoring simplicity above completeness - making the implementation simple really made the whole thing possible. 

But thinking about the example from the essay - the "interface" for the PC loser-ing was "more complex" because the programmer had to do more checking. And the metaphor for me is, my "interface" is in some ways "more complex" because the user might have to scale/crop the image on their own. But, the UI itself reflects the simplicity of the interface! Which in turn means the user has to know less about my system - it's more transparent, easier to keep a mental model of what's going on. 

Sunday, March 28, 2021

rarely happier

My sweetie Melissa found some scratchers left over from Christmas - 2 of them, $2,000,000 50x Cashword are a little annoying to check - you have to hunt through a completed crossword to see if you actually had the letters for any of the words (SPOILER: thanks to the missing vowels you will likely complete about a word per puzzle but you need two in a puzzle to win back your money, but if you get up to eleven you win big bucks)

Anyway, in the words of Douglas Adams who said he was "rarely happier than when spending an entire day programming my computer to perform automatically a task that would otherwise take me a good ten seconds to do by hand.” I made a little Javascript checker for it...  I really enjoy being able to whip something like this up.

Friday, March 19, 2021

regexcellent? regevenbetter

I mentioned my regexcellent tool before, for running simple search and replace javascript expressions. 

I guess it's one of the reasons I don't care if my editor has good macro recording/playback any more. 

Anyway I just made a version so it can leave out lines that don't match.

Wednesday, March 17, 2021

automating SQL with GTP-3 (AI machine learning/languge tools)

Ran into this a while back:  Automating my job by using GPT-3 to generate database-ready SQL to answer business questions. "Natural language programming" has been a long sought-after goal and this is impressive but I think it will be a while before we trust the results!

Tuesday, March 16, 2021

hall of shame: evernote's global keyboard chutzpah

yikes, is it just me or is this hella presumptuous, to add a new global keyboard shortcut just for your app?



Monday, March 15, 2021

the joy of trivial webapps in php - calorie tracker with graph

I'm pretty open with the idea that, if I didn't exercise care, my weight would go up. (And I don't judge people who are heavier than they'd prefer! It's super tough.)

Actually for a long time I've been tracking my weight and even sharing the graph online: diet.kirk.is goes all the way back to 1999!

Here's what I said about weightloss in 2018 and I think is still true:
Still the best diet plan comes from the book "Chubster: A Hipster's Guide to Losing Weight While Staying Cool" and also "The Hacker's Diet": "find a method to hit a daily calorie count without making yourself miserable" I doubt there's an all-in-one for everyone, but so many successful diets boil down to that.
It's the "miserable" bit that's the real sticker, since eating is so emotional! I've had to learn and leverage lots of details about my body's hunger cycle: famished midday, not so hungry in the evenings, not really needing variety but definitely needing its sweettooth satiated.

Anyway, in trying not to gain back ALL the weight I lost over quarantine (24% of people gained weight but 17% lost - again, eating is emotional!) for a few weeks I've been keeping a simple text entry in "Simplenote" to track my eating over the course of a day. (I've used nerdy apps before, doing all those lookups, but they're always so many screens, back and forth, lots of tip tapping... somehow text files just feel nice). Eventually I settled on a "calories - item name" format that was easy to visually scan and run the sums for. 

But then I thought - I can build a simple webpage to help keep track. Here's what I came up with:


As you can see, geared to be mobile friendly at least :-D It makes a graph of what entered previously, it sums the total, there's a little date picker than can change to a different day, as well as a list of entries below. 

Anyway, it's such a delight to be able to spend an hour or so and make this for myself. I wish every one was so empowered! It's just 3 files. Here's the main index.php: (that viewport line helps with the mobile friendly aspect)

index.php:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<?
    if(isset($_GET["date"])) {
        $date = $_GET["date"];   
    } else {
        $date = date("Y-m-d");   
    }
    
    if(!preg_match("/^\d\d\d\d\-\d\d\-\d\d$/",$date)) {
        die;
    }
    
    print "<title>food - $date</title>";
    
    if(file_exists("data/$date")){
        $guts = file_get_contents("data/$date");
    }
    
?>
<link rel="stylesheet" href="style.css" />
</head>
<body>
  <div class="content">
        <h1>food - <? echo $date; ?></h1>
        <table class="chart">
        <?
            if(isset($guts)) {
                $lines = explode("\n",$guts);
                $total = 0;
                foreach($lines as $line) {
                    if(preg_match("/^(\d*) \- (.*)$/",$line,$matches)){
                        $cal = $matches[1];
                        $total += $cal;
                        $width = $cal / 5;
                        $food = $matches[2];
                        print "<tr><td>$food</td>\n";
                        print "<td><div style='width:".$width."px'></div></td></tr>\n";
                    }
                }
                print "<tr><td><br><b>total: $total</b></td></tr>";                
            }      
        ?>
        
        </table>
        
        <form action="save.php" method="post">
            <input name="date" type="date" value="<? echo $date; ?>" 
                onchange="window.location='./?date='+this.value">
            <textarea name="guts"><? echo $guts ?></textarea>
            <button>submit</button>
        </form>
  
        <ul>
        <?
            $files = array_values(array_diff(scandir("data"), array('.', '..')));
            foreach($files as $date){
                print "<li><a href='./?date=$date'>$date</a>";   
            }
        
        ?>
        </ul>
  </div>
</body>
</html>
I depend on security through obscurity and htauth for this kind of stuff. Since I am writing/reading files on my webserver, I do sanity check that files names match ####-##-## formats only. I like the little "onchange" I made with the html5 date field to navigate, and the graph-in-table is reasonably elegant. 

There's also a tad of CSS:

style.css
body {
    background-color: white;
    color: black;
    font-family: sans-serif;    
}
.content {
    width: 800px;
    margin: auto;
}
input,textarea {
    display:block;   
    margin-bottom:10px;
}
textarea {
    width:20em;
    height:20em;
}
button {
    font-size:40px;   
}

.chart div {
    height:20px;
    background-color: #ccc;
}
Slightly clever using display:block,I guess, to get things to line up smartly.
And then the file saving is trivial, just save the file and go back to the day, which will display it.

save.php
<?
    $date = $_POST["date"];
    if(!preg_match("/^\d\d\d\d\-\d\d\-\d\d$/",$date)) {
        die;
    }
    $guts = $_POST["guts"];
    file_put_contents("data/$date",$guts);
    header("Refresh:0; url=./?date=$date");
?>

UPDATE:

Not that anyone cares but I updated this a little, changed the order of things so the textarea was first in the tab order and did a

onfocus="this.selectionStart = this.selectionEnd = this.value.length;"
to hop the cursor to the end, and made the regex more robust:

"/^(\d+) ? ?\-? ? ?(.*)$/"
The lets it use space dash space, but omit either the dash or the space, or add an extra space or two.

Sunday, March 14, 2021

make a downloadable text file in javascript (no server)

Continuing my Atari kick... I've started work on title-screen-titan, a browser tool for the esoteric art of making 48-pixel graphics on the Atari 2600. 

One thing I like to do with my Atari tools is provide code you can run right away. 

This page taught me how to build up one click downloads without hitting my server:

function download(filename, text) {
  var element = document.createElement('a');
  element.setAttribute('href', 
    'data:text/plain;charset=utf-8,' 
        + encodeURIComponent(text));
  element.setAttribute('download', filename);

  element.style.display = 'none';
  document.body.appendChild(element);

  element.click();

  document.body.removeChild(element);
}

Then I just called download() in the onclick of a button. 

I also updated my old batari Basic kickstart tutorial - I do love writing to help people learn to do new stuff.

Wednesday, March 10, 2021

the joy and longevity of vanilla javascript

 In 2005 I wrote a sprite editor for the Atari 2600, PlayerPal 2600.

This was before jQuery, so I wrote it in Vanilla Javascript. (And, gasp, tables)

In 2008 I upgraded it so it could output "Batari BASIC" code - so the editor plus bB is probably the easiest way of getting an Atari 2600 program up and running.

Anyway, this year a fellow named Oliver Gross submitted some code improvements allowing height and color info to be preserved when re-importing assembly data.... smartly, my past self tagged each line with color data but dumbly thought "well it's too hard to bring [the real] color data back in so I'm not going to try". Gross' code just reads the comments. It's a great 80/20 solution - if I'm not going to put in a clever parser, this still allows for people to iterate on their sprites!

I wrote it as a single file - no external libraries or graphics. (A concept that puts it in the tradition of type-in games from computer magazines) I think having it as a single file made it very easy for Gross to set up a "dev environment" and then submit changes, even without github! He just had a to view source, copy and paste all into an editor, save it, then load that in a browser. And then send me the end file, which I diff'd with the current code to review the changes.

It's funny how this predated jQuery, and outlived jQuery as well! Vanilla js is cool like that.

Tuesday, March 9, 2021

UI Hall of Shame: Outlook ctrl-Z after delete

As far as I know, Outlook is not widely beloved. Probably not as bad as it used to me, but its like it never quite caught up to the UX Gmail introduced for keeping threads manageable.

So sometimes I'm tearing through my email, delete delete delete... sometimes I hit delete, then an instant later realize no, I wanted to read that one. Ctrl-Z brings it back... but doesn't jump to the thing I just deleted, and because of the weird way things are sorted, I don't always know how to get back to it...

Ctrl-Z should absolutely restore the full state, not just the more important part...

Sunday, March 7, 2021

php was such a nice step over perl

I get a little defensive over my use of PHP (and keep a link Taking PHP Seriously handy) - I sometimes wish my rented server had more options for doing serverside stuff in javascript, say, but PHP is both convenient and performant.

My server-based blog/diary/database still uses a lot of legacy Perl CGI - if it ain't broken why fix it? - but lately it HAS been breaking, logs showing "Cannot allocate memory: couldn't create child process" errors. So I figured it was time to update things to PHP and get rid of the CGI dependency. 

(I still owe a debt to Perl - see what perl taught me (in the early-mid-90s))

One thing about PHP - it's such a mature environment, in terms of almost every utility you'd want is baked into the language - no CPAN or npm hell or hacking up some regex, it's just there. Case in point, here's a legacy Perl function for my private diary (a bunch of flat files, one per day) I'm porting now

sub dateGoneOrEmpty{
    if(! (-e "entries/$justwhen.txt")){
	return 1;
    }
    open(READ,"< entries/$justwhen.txt");
    my $buf = "";
    while(defined($line=<READ>)){
	$buf .= $line;
    }
    close READ;

    if($buf =~ /MORE/){
        return 1;
    }

    if($buf =~ /\S/){
	return 0;
    }
    return 1;
}

The "$justwhen" is a datestamp used as filename... this function returns 1 if the file doesn't exist, is empty, or just has the word (all caps) MORE in it (an old hack I used when I knew I wanted to come back and put in more details in a day's entry)

But the syntax is kind of terrible! It's Perl's wonky/shell based way of passing in variables, rather arcane "-e" for file test, use of 1 or 0 for true/false, and doing everything by regex (well that last is not terrible terrible but still) That all reflects Perl's history drawing from shell programming, which I never did much of.

Here is the same logic recreated in PHP

function dateGoneOrEmpty($justwhen){
    $filepath = "entries/$justwhen.txt";
    if(!file_exists($filepath)) return true;
    $buf = file_get_contents($filepath);
    if(str_contains($buf,"MORE")) return true;
    if(ctype_space($buf)) return true;
    return true;
}

(Not optimized at all, just trying to formally recreate the old behaviors)

That's so much cleaner! I also love how PHP splits the difference with globals... you can just write a simple script and everything is a global, but if you reference a global in a function - you can do that, you just have to declare it as "global". That's so much better than javascript's "well we'll just throw it on window." and/or  "just assume there is a global of this name"

(The one thing I loved in Perl I wished showed up elsewhere: use of "eq" and "ne" "gt" and "lt" for string comparisons... I love the mnemonics - it's a string, so the operator is more string-ish)


Thursday, March 4, 2021

note to self: bold tags can be used to select different font files

I implemented support for customizing font-family's with the assumption I could use the font-family name as a unique key, but that was a misthink; I'm not sure it's frequently advisable, but there's a use case of having different font-families where like you pick one file or another based on, say, a <b> tag:

I made up a simple test page

@font-face {
    font-family: 'Foo';
    src: url('AkayaTelivigala-Regular.woff2') format('woff2'),
        url('AkayaTelivigala-Regular.woff') format('woff');
    font-weight: bold;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Foo';
    src: url('Stick-Regular.woff2') format('woff2'),
        url('Stick-Regular.woff') format('woff');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

Later on then, this HTML uses two different fonts files:

<div class="foo">Same Font Family <b>
But A Different File For Bold</b></div>

Of course, the browser can do a pretty good job faking up a "bold" even if it's coming from the same file.

Wednesday, March 3, 2021

overlapping sprints to deal with the harsh realities of scrum

Scrum, an "agile" methodology to make the rhythm of software development more predictable, tends to be built on some iffy assumptions.

Some of these methodologies assume that any given ticket will be a equally as difficult no matter which dev, qa, or designer works on it. (Kanban, in its purest form of "the top ticket is the most important thing that should be picked up next" also falls prey to that 'everyone is interchangeable' fallacy, but in practice people don't have to scoop the ticket right on the top, they'll go down a few to find one that makes sense for their role.)

Some teams avoid thinking of people as interchangeable parts, but fall prey to completely ignoring the "Gant chart"-like dependencies within a sprint - the way many tickets will need some work by Designers, which will then be handed off to be implemented by Developers, and then finally to QA (with some time needed for Dev to fix what QA finds)

(But at least such teams have QA has its own thing! - the trendy thing to do has been to say "devs QA their own work" - armed with their own sense of diligence and the power of automated tests, but A. it's really hard to get someone to be good at hunting for something they don't want to find, i.e. ego-bruising bugs in their own code and B. for automated tests.... if they could have thought of went wrong ahead of time they would have already fixed it in code! So tests are mostly good to say what was developed hasn't been broken over time. (There are some other things tests are good with, but still, in my experience they aren't as load bearing as people want to pretend, and are much more brittle and prone to needing to be fixed than the code itself.))

Anyway. Here's theoretically what a sprint should look like:


That's a weird setup, right? What is QA doing the first part of the sprint? What is Design doing the last? In practice there are always things to be doing, extra chores, light prep for future sprints or learning or useful tasks, but I find it bizarre this kind of dependency isn't accounted for in the system. And of course, let's be honest, thanks to human psychology, this is more what a sprint actually looks like:

QA always gets squashed! Devs are thinking more in terms of the whole sprint rather than aiming to get done halfway through. And so the system fixes itself by carrying over to the next sprint... but that feels bad, like failure, even though the failure is more in the design of the system than in the people doing the work.

The sanest, most clear-eyed response I've seen to these problems was at Alleyoop, a little subproject of Pearson Education that unfortunately never found its sustainable business model mojo. They would do overlapping springs, something like this:


I'm not sure how the naming actually worked (overlapping full sprints vs "subsprints") but hopefully you get the idea: from the devs point of view, at any given sprint, designers were prepping stuff for dev that would be coded in the next sprint, and QA was checking on what was dev'd up the previous sprint.

The trouble is, most tools companies rely on don't model this particularly well. (Alleyoop was well in pre-COVID times and independent, so it was free to use good old "stickies on a white board" without having to use online tracking tools.)  And it's a tough sell. But at least it acknowledges that any given ticket has dependencies in time, and if you want to keep everyone productive, you should include those dependencies in your system.

Monday, March 1, 2021

Sans Forgetica, a font to remember

 Sans Forgetica, a font to remember


Having to fill in the gaps might engage better, more remembering parts of your brain! Interesting free font.

change your apple pencil tip

 I haven't thought much about Apple Pencil tips - honestly they seem to last a long time! And it wasn't clear what was "consumable" about them, but after noticing very bad responsiveness, ordering new ones, and then reading 5 Signs it’s time to change your Apple Pencil Tip - I finally notice what's happening, that you don't want a hard surface that might scratch your iPad screen, so the tips give way first, but slowly. 

The "Pencil" name is more appropriate than I realized!

Anyway, they're pretty cheap to order online.

Wednesday, February 24, 2021

cool storybook cookbook

This page of storybook HOWTO/recipes lets us do a trick in storybook... we were already using a set up in preview to style components, but we wanted to be able to include 

<Canvas>

  <Story name='Some specific argument' args={{ children: 'Button' }}>

without that Story showing up in the sidebar. (Detailed request from our PM)

Tuesday, February 23, 2021

two pieces on keeping it simple

I am not a fan of frameworks in general. 

Here's a piece on The web didn't change; you did and reminding folks that for personal projects, nothing is keeping you from PHP/HTML and Javascript. 

Even for more professional settings, there's this piece on The Case Against Web Frameworks. Browsers can do so much today, and ECMAScript/JS has so much great syntax built in.

I've been using PHPwebhosting for over a decade now (and a similar site before that.) It's such a great investment, a site I can ssh into and put up whatever fool PHP I want! 

I worry about the disconnect between the tools I use "for fun" and the stuff I use for work - one of my secret sauce advantages over the years was using the same kind of stacks for both - but it is what it is. And maybe I'll slowly convince people at work to keep it simple as well...

saul bass in the place, saul bass kicks some... butt

Monday, February 22, 2021

half a billion to learn that UI matters

Citibank just got a $500 million lesson in the importance of UI design.

Actually maybe that bubbles up to UX as well; besides a crap UI, there was a lot of complexity in understanding account type and what not.

Saturday, February 20, 2021

du sort human-readable diskusage

 Something I remember looking up before a few times:

du -hs * | sort -h

(via this serverfault question)

That gets disk usage 'h'uman readable (i.e. Gb, Mb, K at different scales),  's'umarizes (so you don't get the detailed listing for every subdirectory) and then sort has a parallel 'h'uman flag to deal with the readable values.

Saturday, February 13, 2021

how to draw and manage a hex grid on a computer

One of my favorite board games is Hey, That's My Fish! It's a really cute game where color-coded teams of penguins slide around a hexagon-based ice field. Each piece of ice has 1 2 or 3 fish, and every time a penguin exits a piece the penguin claims the fish, but the piece melts, so the board is ever changing, and there's a lot of strategy in trying to block other penguins with melted ice and penguin bodies. 

It's a strategy game, but not TOO think-y, or too much secret information to keep track of, which is a good balance for me.

There's an excellent version for iOS (it's ideal as an iPad party game) and Android, but also a free online version at yucata.de. The latter is at a really nice and congenial site (the Germans love their board games) but the interface is a little clunky for Zoom/video conference play... it's a bit geared at remote players taking turns at their leisure, and the whole site is a bit Web 1.5 feeling and requires accounts to be set up, so I'm not sure if I feel comfortable recommending it for casual work get togethers.

(From a UX standpoint, one really nice feature at the Yucata version is it uses colors to highlight the last move each player made... great when a game is going fairly slowly or people aren't being 100% attentive)

I'm not sure if it's worth coding my own version of the game (like I did for the NSFW card game Joking Hazard ) especially since it only goes up to 4 players. (If it went up to 6 or 8 it would be perfect for work game night...) But, the idea of teaching a computer to do a hex grid was interesting! Many strategy games I grew up with used hexes, (I especially remember Steve Jackson's OGRE)... there's more symmetry in the 6 directions (vs square grids, where diagonal moves go a lot further than the cardinal directions)x

As my friend Jon pointed out, a hex grid can kind of be represented as a plain old square grid, with every other row offset, but still since each hex has 6 neighbors and each square grid has 8, it was worth sketching things out and trying to get a sense of how things flowed when you treated a hex grid in terms of x,y coordinates...

I do love Apple Notes App for this kind of thing!

Interesting - every hex has East/West and North/South neighbors, but when coerced to a square grid, like... G has neighbors to the NE and SE (namely C and M) but M, one row down, has NW and SW. Every other row, then, acts a bit differently (which is why it seemed like mod arithmetic might do the trick, as we'll see later on.

I don't have super-strong spatial intuition, so I decided to just start coding incrementally rather than planning everything out. And I decided to code things up in P5, using its convenient editor. 

My first program just drew a hex grid. To be frank I just dabbled numbers until things looked right.
I started using a convention of "i,j" for the hex location, and "x,y" for the coordinate on screen. Empirically I found out if "BASESZ" was the base width... every other row had to be offset, hence

x = (i + (j % 2) / 2) * BASESZ 

and the y was packed a little tighter, based on how I was drawing hexes:

(I didn't bother to check if these were "perfect" hexes, frankly if they're a little squashed it's fine)

For my second program, I started using a new data structure, just a flat list of all hexes... not quite object oriented per se, but each hex knew where it was located and could draw itself. I also draw a circle inside each hex... it was easier to do a simple pythagorean dist() to see if the mouse was over the thing than any fancier math. On every mouse move, I reset each hex's "on" key to false, and then for each hex if it's in the distance it gets set to true.

Hex Experiment 3 finally gets a little interesting. Penguins in "Hey, That's My Fish!" move a bit like a queen or rook in chess, as far as they want in any of the 6 directions (but can be blocked by missing hexes or other penguins) so I thought I'd show what moves were available from whatever hex the mouse was on:

Besides the previous version's flat list, I stored each hex in an [i,j] two dimensional array, (It's a little weird when rolling your own 2D array, Rows come first, then Columns... at least the way I think about it) I then made 6 functions, each one returning an array of hexes leading out in that direction - basically a walker loop start at the base hex and proceeding whatever direction. East and West were easy, but NW/NE/SW/SE needed a little finagling... to look up the neighbor in the row above it was moving leftor right, like for southwest the change in i (for horizontal position changed)

i -=((j+1)%2);

where j is the current row. By making it visual, it was pretty quick to figure out what the math should be - a crude but effective way of getting it right.

Finally Hex Experiment 4 uses a slightly better syntax, letting each directional be a call to the same function with just a "incrementI, incrementJ" pair of functions. That way if I ever do make penguins, I only have to fix the loop to get blocked by other penguins or missing hexes in one function instead of 6.

For safety, here's the final code:


const BASESZ = 40;

const COLCOUNT = 8;
const ROWCOUNT = 8;

const hexList = [];
const hexGrid = [];

const SHIFTLEFT = 50;
const SHIFTDOWN = 100;

const OFF = 0;
const ON = 1;
const OVER = 2;

function setup() {
  createCanvas(400, 400);

  for (let j = 0; j < ROWCOUNT; j++) {
    hexGrid[j] = [];
    for (let i = 0; i < COLCOUNT; i++) {
      const hex = makeHexForLoc(i, j);
      hexList.push(hex);
      hexGrid[j][i] = hex;
    }
  }
}

function draw() {
  background(220);
  push();
  translate(SHIFTLEFT, SHIFTDOWN);
  hexList.map((hex) => {
    drawHex(hex)
  });
  pop();
  noLoop();
}

function makeHexForLoc(i, j) {
  const x = (i + (j % 2) / 2) * BASESZ;
  const y = (BASESZ * 0.75) * j;
  return {
    i,
    j,
    x,
    y,
    mode: OFF
  };
}



function mouseMoved() {
  hexList.map((hex) => {
    hex.mode = OFF;
  });

  const boardX = mouseX - SHIFTLEFT;
  const boardY = mouseY - SHIFTDOWN;

  let pickedHex = null;
  hexList.map((hex) => {
    if (dist(boardX, boardY, hex.x, hex.y) < BASESZ / 2) {
      pickedHex = hex;
    }
  });
  if (pickedHex) {
    //Check Easts
    getOpenHexesInDir(pickedHex,()=>1,()=>0)
    	.map(hex => hex.mode = ON);
    //Check Wests
    getOpenHexesInDir(pickedHex,()=>-1,()=>0)
    	.map(hex => hex.mode = ON);
    //Check North Easts    
    getOpenHexesInDir(pickedHex,(i,j)=>j%2,()=>-1)
    	.map(hex => hex.mode = ON);
    //Check North Wests    
    getOpenHexesInDir(pickedHex,(i,j)=>-(j+1)%2,()=>-1)
    	.map(hex => hex.mode = ON);
    //Check South Easts
    getOpenHexesInDir(pickedHex,(i,j)=>j%2,()=>1)
    	.map(hex => hex.mode = ON);
    //Check South Wests    
    getOpenHexesInDir(pickedHex,(i,j)=>-(j+1)%2,()=>1)
    	.map(hex => hex.mode = ON);
    pickedHex.mode = OVER;
  }
  loop();
}


function getOpenHexesInDir(hex,iTransform,jTransform){
  let {
    i,
    j
  } = hex;
   const res = [];
  while (i < COLCOUNT && j < ROWCOUNT && 
  	i >= 0 && j >= 0) {
    res.push(hexGrid[j][i]);
    i += iTransform(i,j);
    j += jTransform(i,j);
  }
  return res;
}

function drawHex(hex) {
  const {
    x,
    y,
    mode
  } = hex;
  const sz = BASESZ / 2;
  push();
  translate(x, y);
  //sides
  line(-sz, -sz / 2, -sz, sz / 2);
  line(sz, -sz / 2, sz, sz / 2);

  //top
  line(-sz, -sz / 2, 0, -sz);
  line(0, -sz, sz, -sz / 2);

  //bottom
  line(-sz, sz / 2, 0, sz);
  line(0, sz, sz, sz / 2);

  fill(255);
  if (mode === OVER) fill(255, 128, 128);
  if (mode === ON) fill(128, 128, 255);
  circle(0, 0, sz * 1.5);

  pop();
}

I'm not sure if I'll ever make the online version of this game, but this was a satisfying bit of computer math to get done on a Saturday morning.