Wednesday, January 31, 2024

sRGB with the DCM

 I was doing some good old fashioned "Digital Color Meter"ing (a Mac app) to pluck colors out of a screen mockup - I'd get the hex values, put them into an HTML document, and then they were visibly darker... and DCM confirmed, it was a different shade reading out than I was putting in.

Turns out, you really gotta get the right setting for the output display:

"sRGB" seemed to get the most accurate color readings... "In the world of web browsers, the sRGB color space is the standard."

the UX and Community-eXperience of remote vs in-office

There’s More Proof That Return to Office Is Pointless - I have mixed feelings on the "Return to Office" debate. Starting my current second Remote job in a row, I appreciate the flexibility of my daily schedule and lack of commute - but I also sometimes resent how my company partially "owns" my office space and makes it a little less fun for my personal side projects.

I'm also aware of how many companies and teams haven't leaned into building a sense of online community. There's a UX story here: achieving a virtual group presence and ongoing conversation is a harder row to hoe with MS Teams than some other platforms. Each Teams space feels like a collection of public email threads (with a clunky Word-like formatting tool ribbon front and center) vs the more chat-y experience of Slack, where there is a single conversation with threads branching off.


(It seems Google Chat recently pivoted to be more like Slack - so old threads with new replies are no longer moved up. But Slack has a gloriously clever bit of UX with a checkbox to copy any given thread reply to the main channel - this empowers a user to selectively bring up an old conversation that might have dropped off other folk's radar.)


Of course, the tech matters a bit less than the mood of the folks using it - it's a cultural thing, and I'm not sure yet if that best comes from the top down or can be made organically. It's a little disheartening when you see a bunch of unused channels, where the last post was months and months ago. (I think one UX takeaway there is don't get TOO fine-grained with your room/channel creation. In theory specialty topic channels might encourage discussion in that area, but in practice it can dilute an already small pool of participants.)


Getting back to other Hybrid and In-Office concepts, I do worry that as a remote worker I am more of a fungible commodity than I would be otherwise. No matter how one feels about a bias against remote work, living near Boston, I wonder if there's a competitive advantage to having hybrid or in-office as an option? One that personally I would have been ok with, though I like companies that respect employee's countering opinions on the matter.

Tuesday, January 30, 2024

new CSS goodness

12 Modern CSS One-Line Upgrades. CSS is getting good, and automatic browser updates means you can leverage this stuff.

Thursday, January 25, 2024

swipe before/after slider in react and elsewhere

For work we wanted to look into making a side-by-side/before-after slider/swipe tool thing. There are number of those available for React, but many of them seem hard coded to do images only, not arbitrary DOM stuff.

As a warm up I handrolled a version in P5.js - it's still just using images but it gave me an idea of what I was up against. (It's sneakily tricky to make a cut out like this-- since they virtually occupy the same space, you can't just have one in front of the other.)

I found two ways to do it in P5, where "mid" is the X-coordinate change over point:

You can draw the left image, and then start drawing the right image at mid, making sure to draw from the offscreen image at that point only:  

image(imgBefore, 0, 0,width,height);
image(imgAfter, mid, 0,width,height,mid,0,width,height)

or you can draw the right image, and then just part of the left image:

image(imgAfter, 0, 0,width,height);
copy(imgBefore, 0, 0, mid, height, 0, 0, mid, height);

But of course we wanted to do it in React. I fired up ChatGPT to give a hand and came up with the content of this CodePen. (I also did a version with the images just so it looked better)

The JS was mostly about the slider control and the jokey content, but it had that critical clipPath which was the special sauce

const { useState, useRef } = React;

const BeforeAfterSlider = () => {
  const [sliderPosition, setSliderPosition] = useState(50);
  const sliderContainerRef = useRef(null);

  const handleMouseDown = () => {
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  const handleMouseMove = (e) => {
    const rect = sliderContainerRef.current.getBoundingClientRect();
    let newPosition = (e.clientX - rect.left) / rect.width * 100;
// Clamp between 0 and 100
    newPosition = Math.max(0, Math.min(newPosition, 100)); 
    setSliderPosition(newPosition);
  };

  const handleMouseUp = () => {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
  };

  
const SampleContent = ({className}) => {
  return <div className={className}>Lorem ipsum dolor sit amet, 
  consectetur adipiscing elit, sed do eiusmod tempor incididunt 
  ut labore et dolore magna aliqua. Ut enim ad minim veniam, 
  quis nostrud exercitation ullamco laboris nisi ut aliquip ex 
  ea commodo consequat. Duis aute irure dolor in reprehenderit 
  in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
  Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 
  officia deserunt mollit anim id est laborum.</div>; 
}
  
return (
  <div className="slider-container" ref={sliderContainerRef}>
    <div 
      className="before-content" 
      style={{ clipPath: `inset(0 ${100 - sliderPosition}% 0 0)` }} 
    >
      <SampleContent className='beforeGuts'/>
      </div>
    <div 
      className="after-content" 
      style={{ clipPath: `inset(0 0 0 ${sliderPosition}%)` }} 
    >
    <SampleContent className='afterGuts'/>
      </div>
      
    <div
      className="slider-handle"
      style={{ left: `${sliderPosition}%` }}
      onMouseDown={handleMouseDown}
    />
  </div>
);
  
};
ReactDOM.render(, document.getElementById('root'));


oh and there was some CSS

.slider-container {
  position: relative;
  width: 100%;
  max-width: 600px; 
  height: 300px; 
  overflow: hidden;
}

.before-content,
.after-content {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-size: cover;
  background-position: center;
  clip-path: inset(0 50% 0 0); /* Initially half */
}

.before-content {

}

.after-content {

  clip-path: inset(0 0 0 50%); /* Initially half */
}

.slider-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 5px;
  background: black;
  cursor: ew-resize;
}

.beforeGuts,.afterGuts{
  font-size:20px;
  font-family:sans-serif;
  font-weight:bold;
  width:600px;
  background-color:white;
}

.beforeGuts {
  color: red; 
}
.afterGuts {
  color: blue; 
}

Took some iterating with ChatGPT but we got there.


UPDATE: here is my final version using Styled-Components, and changing the interface to use the first 2 children....(note with styled components I had to use to the attrs() instead of creating new classes each time the slider moved:


import React, { useState, useRef } from 'react';

import styled from 'styled-components';

// Styled components
const SliderContainer = styled.div`
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
`;

const Content = styled.div.attrs((props) => ({
    style: {
        clipPath: props.clipPath,
    },
}))`
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-size: cover;
    background-position: center;
`;

const SliderHandle = styled.div.attrs((props) => ({
    style: {
        left: props.left,
    },
}))`
    position: absolute;
    top: 0;
    bottom: 0;
    width: 5px;
    background: red;
    cursor: ew-resize;
    display: flex;
    align-items: center;
`;

const SliderHandleCircle = styled.div`
    width: 40px;
    height: 40px;
    background-color: transparent;
    border: 6px solid red;
    border-radius: 50%;
    position: absolute;
    left: -18px;
`;

// we are assuming exactly two children
const SwipeSlider = ({ children }) => {
    const [sliderPosition, setSliderPosition] = useState(50);
    const sliderContainerRef = useRef(null);

    const [leftContent, rightContent] = children;

    const handleMouseDown = () => {
        document.addEventListener('mousemove', handleMouseMove);
        document.addEventListener('mouseup', handleMouseUp);
    };

    const handleMouseMove = (e) => {
        const rect = sliderContainerRef.current.getBoundingClientRect();
        let newPosition = ((e.clientX - rect.left) / rect.width) * 100;
        newPosition = Math.max(0, Math.min(newPosition, 100)); // Clamp between 0 and 100
        setSliderPosition(newPosition);
    };

    const handleMouseUp = () => {
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
    };

    return (
        <SliderContainer ref={sliderContainerRef}>
            <Content className="left-content-wrapper" clipPath={`inset(0 ${100 - sliderPosition}% 0 0)`}>
                {leftContent}
            </Content>
            <Content className="right-content-wrapper" clipPath={`inset(0 0 0 ${sliderPosition}%)`}>
                {rightContent}
            </Content>

            <SliderHandle left={`${sliderPosition}%`} onMouseDown={handleMouseDown}>
                <SliderHandleCircle />
            </SliderHandle>
        </SliderContainer>
    );
};

export default SwipeSlider;

Wednesday, January 24, 2024

switch

At my new job I'm asked to use Windows, which I haven't used on the regular for over a decade. (Though it seems like new versions of Windows got rid of the bloatware and weird early touchscreen UI issues)

I am nervous but I like CreativePlankton's thought on this reddit thread on the topic:

I'm an IT guy born and raised on the PC, and hated Apple. Then I got a job where the company was 60% Mac and 40% PC. I figured I better learn the Mac. After a couple of weeks I felt pretty comfortable with the Mac. After a couple of months I gave no thought to the computer, just the task that needed to be done. It's kind of like driving two different cars. The various controls are in different places, but both have basically the same things. Personally, I now prefer the Mac, but seriously don't over think it. If you're in tech for any length of time knowing multiple platforms will serve you well.
I still have to find replacements for a ton of little helper programs.


UPDATE:
Pivoting back to Windows (after ten years!) to align with my company; it's better than I had feared but I'm still surprised my desire to use the scroll wheel up to make the document "go up" is so weird none of the archaeological layers of Windows settings covers it; I either have to tweak the registry (!) or install the Logitech management software.


Which movement is "up" for scroll wheels, touchpads, and 3D games - and how different people have different intuitions about how it should be - is an intriguing interesting UX issue I wrote about in my devblog a few years ago

Monday, January 22, 2024

so you think you know C

So You Think You Know C - this is what those JavaScript WAT (usually about truth-y values) feels like to me. Yes, some bits are wonky, but honestly 90% of the errors I make for myself because of loose typing, say, would be fixed if "+" wasn't both "addition" and "string concatenation"

Saturday, January 20, 2024

from perl to php to life with ChatGPT

On a private Slack, I quoted this line from yesterday's email domain validation learning -
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
(so filter_var returns false if it's not a match) and a friend pointed out:

`filter_var()` is exactly the kind of API abomination PHP is known for, a+++ language design

He's not wrong! That's pretty ugly - the name of the function is kind of negative, so you're asking people to think in double negative to use it properly (vs a more typical isValid() type naming)

I was trying to reconstruct my history with PHP; I taught myself Perl in college, and .cgi saw me through a number of years. PHP was a boon because the useful functions were built in - often installing libraries for Perl required root access I didn't always have, and for a long time I was always copying this boiler plate into my CGI scripts:

sub chugCGI { local (*in) = @_ if @_; local ($i, $key, $val);
          if($ENV{'REQUEST_METHOD'}eq"POST") { read(STDIN,$in,$ENV{'CONTENT_LENGTH'});
                           }else {$in = $ENV{'QUERY_STRING'};} @in = split(/&/,$in);
          foreach $i (0 .. $#in) { $in[$i] =~ s/\+/ /g;
($key, $val) = split(/=/,$in[$i],2);$key =~ s/%(..)/pack("c",hex($1))/ge;
$val =~ s/%(..)/pack("c",hex($1))/ge; $in{$key}.= $val;}return length($in);}


which read CGI variables and put them in a assoc. array when CGI.pm or CGI_lite.pm wasn't at hand.
So PHP was obviously a step up. But when I first tried learning it (in the first dot com crash) around Y2K, it really was even less ready for prime time - IIRC if you walked an array you had to like reset an iterator that was part of the array somehow? and 2 dimensional arrays were a right mess, and the documentation was wrong... basically it felt slapdash in a way that Perl never did, even though later I found out Perl was actually a glue language over C Unix System Calls - which blew my mind because I thought Perl (with its first class strings and associative arrays and lack of memory management, or compiling for that matter) was like that anti-C.

But over the years it became my default for backend stuff - the Apache monolith LAMP model just gets out of the way of the front end stuff I really care about (and I still direct folks to the "Taking PHP Seriously" article when feeling defensive about that) but the weird thing was - since I learned it in a google era, I hardly memorized any of it. Its API does have a lot of weird edges (especially in terms of if you have a function with arguments of an array and a single member, which argument is likely to appear first? for in_array() the needle comes before the haystack, but for array_push() the array comes first... which makes some kind of sense in terms of allowing you to add multiple things at once but still doesn't scream consistency.) But still, relative to Perl, there were so many useful functions builtin... and also it didn't have all that weird syntax that makes "Perl golf" a hobby of some.

And yet, I had most everything in Perl memorized in a way I never have with PHP... but now rather than googling to find my PHP bit on stackoverflow, I have ChatGPT. Like David Winer put it:

ChatGPT is like having a programming partner you can try ideas out on, or ask for alternative approaches, and they're always there, and not too busy to help out. They know everything you don't know and need to know, and rarely hallucinate (you have to check the work, same as with a human btw). It's remarkable how much it is like having an ideal human programming partner. It's the kind of helper I aspire to be.
or as Salvatore Sanfilippo put it:
In the field of programming, perhaps [LLM's] ability would have been of very little interest up to twenty or thirty years ago. Back then you had to know a couple of programming languages, the classic algorithms, and those ten fundamental libraries. The rest you had to add yourself, your own intelligence, expertise, design skills. If you had these ingredients you were an expert programmer, able to do more or less everything. Over time, we have witnessed an explosion of frameworks, programming languages, libraries of all kinds. An explosion of complexity often completely unnecessary and unjustified, but the truth is that things are what they are. And in such a context, an idiot who knows everything is a precious ally.
I'm starting a new job and exploring new territory for me and I am grateful to have ChatGPT by my side.

Friday, January 19, 2024

sanity check to confirm it's an email-able domain

Reading Malcolm McDonald's  Web Security for Developers: Real Threats, Practical Defense for a class I am instructing, he mentioned this Python tidbit that can check if a given domain is likely setup to handle email:
import dns.resolver
def email_domain_is_valid(domain):
  for _ in dns.resolver.query(domain, 'MX'):
    return True
  return False

 

I asked ChatGPT for the PHP equivalent:

function emailDomainIsValid($domain) {
    return checkdnsrr($domain, "MX");
}


and then a version where I can just pass in the full address, and it will get a clean version of the email address, or else return false:

function cleanEmailWithValidDomain($email) {
    // Trim whitespace from the email
    $email = trim($email);

    // Validate the email address
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return false;
    }

    // Extract the domain from the email address
    $domain = substr(strrchr($email, "@"), 1);

    // Check for MX records
    if (checkdnsrr($domain, "MX")) {
        return $email;
    }

    return false;
}


Somehow I missed that you could do that kind of serverside check; I think I read "you can't really tell if an email is legit" too much to heart.

Thursday, January 18, 2024

5 CSS Snippets

 5 CSS snippets every front-end developer should know in 2024 - :has(), subgrid, nesting, new textwrapping, container query units...

I think frontend developers should be grateful that the default is for browsers to autoupdate. It's probably reasonable to assume the great majority of your user are using the updated versions. (Whether we should be worried about the decreasing number of rendering engines is a different story.)

Sunday, January 14, 2024

when "guess what i'd like to do" UX goes wrong

I kind of hate UI that starts to get presumptuous. Like when Google Maps says "Welcome Home" after a trip - cheeky! Or when it wishes me "Bon Appetite" when I finish up a route to a restaurant (what if I was just going for drinks? Or using it as a landmark? What then smart guy?)

And it's worse when an app proposes taska, even when - or maybe especially when - it's based on my past behavior. While it might look good paper, saving steps, in practice "Are You Driving to Work?" can feel pushy and invasive.

But my friend Dylan had a worse situation. He lost his dear friend Kim the other year - a tragically young death. They were besties who lived on opposite sides of the country, and talked and messaged all the time.

A year later, and his Android phone was still featuring her on the "Would You Like to Call?" list. It was heartbreaking! And he actually had to remove her entry from his phone to get it to stop. I was reminded of that guy from the movie Amélie, tearfully erasing his deceased best friend from his address book:


And I know FB has been similarly triggering for folks, especially with it's "Memories" features. Many memories are painful! Maybe on average the feature is an emotional UX win, but it's really fraught. 

(I asked Dylan for permission to tell this story of course... and he pointed out it almost feels like age discrimination, like perhaps someone who has experienced loss of friends or more heartbreak would have made a system that was a little more sensitive to folks.)


Maybe this is an example of AI just being a bit too primitive (a sorted list of "mostly frequently used contacts" is hardly AI at all - for that matter neither would putting in a recency weighting) and better AI could help out - but at the very least UX designers need to include ways to bail out and request certain people or events be excluded...

Tuesday, January 9, 2024

use the new CSS hotness

 CSS is getting more good.  The first two, Container Queries and Scroll Snap, are finally ready to go and seem a lot better than media queries and js hacks respectively.

Monday, January 8, 2024

"back then you had to know a couple of programming languages, the classic algorithms, and those ten fundamental libraries"

Salvatore Sanfilippo's (somewhat dry, in parts) piece on using LLMs as a coding helper matches my experience. I too prefer ChatGPT collaborating from its sandbox vs a helper sliding less predictably into my editor's autocompletes; and it is so very useful as a kind of "self-customizing reference", able to take on some medium complexity challenges but failing on more intricate system (especially when iterating on even its own code, I've found; it can lose track of the original specifications all too easily)

One interesting paragraph was as much about the history of the programming industry as much as it was about LLMs:

In the field of programming, perhaps their ability would have been of very little interest up to twenty or thirty years ago. Back then you had to know a couple of programming languages, the classic algorithms, and those ten fundamental libraries. The rest you had to add yourself, your own intelligence, expertise, design skills. If you had these ingredients you were an expert programmer, able to do more or less everything. Over time, we have witnessed an explosion of frameworks, programming languages, libraries of all kinds. An explosion of complexity often completely unnecessary and unjustified, but the truth is that things are what they are. And in such a context, an idiot who knows everything is a precious ally.

I think that's one of the great dualities in the UI coder industry, right up there with holism vs reductionism: would you as a coder prefer to make something novel in an old-hat framework, or recreate some common interaction using the new hotness? The latter view seems to have won out - there's a lot of neophilia, with the assumption that the leading edge is doing important work that outweighs the benefits of familiarity.


And of course that vast footprint of possible systems to have POSSIBLY worked in seems to make current jobhunts that much tougher (anyone need a strong, veteran UI Engineer with deep React and TypeScript? ;-) ) - those few companies who aren't in hiring freeze (or at least slush) can take their time and look for their unicorns that match.


Saturday, January 6, 2024

firefly

As I continue my hunt for a fulltime job taking advantage of my deep UI Engineering Experience, I've taken a nightshift role as an instructor at Computer Systems Institute. We had our In-Service meeting the other day, and the topic was AI; the possibilities and pitfalls for the teachers and students of the organization. 

Obviously AI should be deeply in the mind of anyone who puts something on a computer screen for a living. While I suspect we might be near the top of an S-shaped curved for performance in this generation of LLM, people will still find surprising new applications for what is available now.

During the discussion I was introduced to https://firefly.adobe.com/  - right up there with Dall-E in terms of capabilities of generating original works. (Also with a unique "Text Effects" mode (shades of the old Micrsoft Word "WordArt" feature) - an endrun around LLM's notable problems in displaying text. ) 

At the very least, these generative art systems are kind of a highly customized replacement for stock clipart. And there are big philosophical issues on the training material used for these systems. I don't want to wave those concerns aside, but it reminded me of when Mark Twain wrote to Helen Keller who was facing charges of plagiarism (of her story "The Frost King" which seems to have drawn from Margaret Canby's "The Frost Fairies", which Keller had "heard" but forgotten about):

Oh, dear me, how unspeakably funny and owlishly idiotic and grotesque was that “plagiarism” farce! As if there was much of anything in any human utterance, oral or written, *except* plagiarism!…For substantially all ideas are second-hand, consciously and unconsciously drawn from a million outside sources.

So I wish I had better answers about what we as individuals, or society, should do in this landscape, but I suspect at the very least these systems are going to be a part of nearly every knowledge worker's toolkit. 

Friday, January 5, 2024

mo' amortization, mo' problems

When going through a prolonged jobhunt, one has to find a balance of introspection (how could I have shaped my career thus far differently and perhaps ended in a more resilient place) but also acknowledge the environmental factors.

Two big whammies in that last category. One is interest rates; money got really, really cheap as we worked to head off a COVID recession. There were some initial shock layoffs, but overall it became a boom time of speculation and growth in tech. That time is done, interest rates are high, and if you're on the outside looking in it is grim.

But there's a might another sea change: Section 174. A Trump-era change (that some expected not to come to fruition) means that software engineer R+D has to be amortized (like, counted as an expense) over 5 years (or 15 years if they're from other countries) - which could be rough for a startup trying to roll the dice and hoping for a quick turn around. Or for people who hope to get hired by that kind of company.

(Or, is it just VCs unreasonably kvetching, in much the same way you companies could do a lot of price gouging and just claim "inflation"?)

Anyway, my usual plug: I'm a UI Engineer strong in React and TypeScript who is looking to help a group make cool, usable stuff.

Monday, January 1, 2024

(REPEAT) one weird trick for making a page think it's somewhere else

Haven't thought of this trick for years, but it's good to know about:

if you're ever updating a page that might, say, usually live on a remote server, but you want to edit it locally, and without pulling down all the css, images, etc, you can tell the page that all relative links actually should be based on a specific URL using the <base> tag... for instance, I'm editing the front of alienbill.com so I put

<base href="http://alienbill.com/">

at the top.

In effect, it makes pretend that the page is at that location for purposes of image references, stylesheets, etc.