Sign in

Kyle Shevlin (he/him)

@kyleshevl.in
15K followers 866 following 4.3K posts

Software engineer. React, Vue, Astro, JS/TS, and all things frontend. Scratch golfer. AuDHD. kyleshevlin.com

PostsRepliesMedia
Kyle Shevlin (he/him) @kyleshevl.in · 01/08/2026
First time in about a year and a half that this floor has… well, flooring. Not sure the paint ideas we had are gonna work out, but hey, floors!
My den with wall to wall LVP flooring installed.
0180
Kyle Shevlin (he/him) @kyleshevl.in · 20/07/2026
Progress on the floor today. I’ve been posting vids on tiktok with tips and lessons learned if you wanna check it out. You can learn what the post-it notes on the boards are for in todays’s vid www.tiktok.com/@_kyleshevli...
About 150 sq ft of LVP flooring down.
180
Kyle Shevlin (he/him) @kyleshevl.in · 18/07/2026
Gotta sort your boards first
8 piles of LVP flooring “boards”.
030
Kyle Shevlin (he/him) @kyleshevl.in · 18/07/2026
Got the thermal break underlayment down. Almost matches some of the paint colors we’re considering.
The floor of our downstairs covered with a thin layer of insulating foam to be a thermal break from the cement slab to our feet.
0110
Kyle Shevlin (he/him) @kyleshevl.in · 14/07/2026
Question for my CSS friends: I have this table in my golf stats app. Look at the cell borders. Any thoughts on how I could improve this? Doing border separate looks horrible no matter what I do, but border collapse doesn't let me control which cell's border wins over its neighbors.
A somewhat colorful table of information related to a round of golf
980
Kyle Shevlin (he/him) @kyleshevl.in · 13/07/2026
Went to work outside on a gorgeous day. Tali had a different idea.
Tali laying across my belly and lap, cuddling
1170
Kyle Shevlin (he/him) @kyleshevl.in · 13/07/2026
Post victory napping
My big chonky cat in my lap in a lawn chair outside
230
Kyle Shevlin (he/him) @kyleshevl.in · 12/07/2026
4x club champ, baby! 🏌🏼‍♂️
Me, holding the winners plaque.
1230
Kyle Shevlin (he/him) @kyleshevl.in · 08/07/2026
I'm a believer in moving your conditions as high up in the code as possible. Conditions at the leaf nodes lead to very difficult updates and maintenance. Even if an AI is doing it, your reviews are more difficult because of this. Move conditions UP!
type Variant = 'featured' | 'user-pick' | 'limited-time'

type Props = {
  variant: Variant
  // the rest
}

// I'm hiding a lot of details, but essentially we have a condition
// spreading out into many places. As this grows, this gets harder to
// understand, and you need shotgun surgery to update changes to 
// conditions, updating every branch the condition creates.
function AdComponent({ variant, ...rest }: Props) {
  return  (
  	<div className={decisionHereAboutClasses(variant)}>
      <div>
        {decisionHereAboutLabelWords(variant)}
      </div>
      
      <div>
        {decisionsHereAboutContentEtc(variant)}
      </div>
      
      <div>
        {decisionsHereAboutActions(variant)}
      </div>
    </div>	
  )
}

// Move the decision up, condition assessed in a single place
// This makes one component per branch, and no downstream branches
// Changes can happen in the specific components without needing to
// impact other variants
function AdComponent({ variant, ...rest }: Props) {
  switch (variant) {
    case 'featured': return <FeaturedAd {...rest} />
    case 'user-pick': return <UserPickAd {...rest} />
    case 'limited-time': return <LimitedTimeAd {...rest} />
    default: return <BaseAd {...rest} />
  }
}
3281
Kyle Shevlin (he/him) @kyleshevl.in · 01/07/2026
The self leveling concrete cured in some cool patterns.
The cured concrete floor with swirls of different shades of gray
0140
Kyle Shevlin (he/him) @kyleshevl.in · 26/06/2026
Tali likes to take naps in our ivy.
My cat tali napping in some ground cover ivy (non poisonous)
080
Kyle Shevlin (he/him) @kyleshevl.in · 23/06/2026
Didn’t play well yesterday in the first round of the Oregon Am, but at least I looked good.
Me, holding my follow thru after a golf shot
1110
Kyle Shevlin (he/him) @kyleshevl.in · 15/06/2026
We’ve had three ~90F days in a row, so I let my lawn grow this weekend so as not to stress it in the heat. This allowed all the clover to flower. The upside is I can watch bees go from clover to clover while I sit here disassociating.
My backyard, the grass covered with clover flowers
0110
Kyle Shevlin (he/him) @kyleshevl.in · 14/06/2026
Heading to a shindig later that I can walk to, and I need a way to carry some beers. So 30 mins and some scraps later…
A 6 pack holder made of wood scraps and sisal rope.
2300
Kyle Shevlin (he/him) @kyleshevl.in · 07/06/2026
Stained and in place now. Time to fill it up with stuff!
The big cedar storage chest I built that doubles as bench, in the sun on my patio.
2190
Kyle Shevlin (he/him) @kyleshevl.in · 26/05/2026
More progress on the storage/seating.
Bottom slats added. Gaps will allow airflow which should prevent anything like mold and mildew from starting (I live in a rainy climate)A lid made of 6 long slats running the length of the storage box.
3200
Kyle Shevlin (he/him) @kyleshevl.in · 24/05/2026
Started working on some outdoor storage/seating out of cedar. A smattering of progress photos. Long way to go.
Side panel dry fitting. Spacer blocks to figure out toe kick height. Front and back panels, minus some vertical stiles for support. All four sides glued and screwed together. Carcass of storage complete.
1150
Kyle Shevlin (he/him) @kyleshevl.in · 17/05/2026
Did a thing today.
Supplies: concrete mic, rebar, gravel and a tamperGround tamped, frame for concrete slab in placeGravel and rebar in the formSlab poured, floated, edged and brushed.
4150
Kyle Shevlin (he/him) @kyleshevl.in · 22/04/2026
Just sharing a good photo @annaeliza.bsky.social took of me and Tali
Tali (cat) standing on my lap as I pet her
060
Kyle Shevlin (he/him) @kyleshevl.in · 03/04/2026
I'm gonna say this having done zero research into solutions, figure you'll tell me. Know how imgs have srcsets, so you can show different imgs at different sizes? I want that for text. My app has all these tables, and I'd love srcsets for the headings at diff sizes.
Table with the headings Score, Fairway, Green, PuttsSame table, but narrower, headings say Score, Fair., GIR, PuttsSame table, so narrow that headings just say S, F, G, P
331
Kyle Shevlin (he/him) @kyleshevl.in · 31/03/2026
If anyone needs some grass to touch, let me know
My cat and I chilling in my backyard, laying in the grass.
1101
Kyle Shevlin (he/him) @kyleshevl.in · 28/03/2026
I know AI is writing all your code, but in case it isn't, y'all probably have been sleeping on flatMap. flatMap let's you filter & map at the same time. Code below has a type error because .find() can return undefined. Turn the map to flatMap, guard/filter undefineds. Types happy.
const nextTees = changes.tees.map(changedTee => {
        const currentTee = current.tees?.find(t => t.id === changedTee.id)

        return {
          ...currentTee,
          ...changedTee,
        }
      })const nextTees = changes.tees.flatMap(changedTee => {
        const currentTee = current.tees?.find(t => t.id === changedTee.id)

        if (!currentTee) return []

        return [{ ...currentTee, ...changedTee }]
      })
2282
Kyle Shevlin (he/him) @kyleshevl.in · 10/03/2026
Smooth walls are cool and all, but knockdown looks pretty cool with the right lighting, too.
A knockdown textured wall I did by hand with some dramatic shadowing due to a down light.
1140
Kyle Shevlin (he/him) @kyleshevl.in · 25/02/2026
Some friends of ours host a “covers only” open mic night at a local bar once a month. They’ve been asking me to come play, even though I haven’t performed any music since ~2013. I finally caved and did a version of Olivia Dean’s “So Easy” last night. Don’t think it was half-bad.
6314
Kyle Shevlin (he/him) @kyleshevl.in · 23/02/2026
Got a little angry at the AI bots trying to tell me something was a bug that wasn't.
One of my git commits that says "I will not be told by claude to add lang=scss when it's unnecessary"
0100
Kyle Shevlin (he/him) @kyleshevl.in · 10/02/2026
I recently learned about "bottom-up thinking" in autism and it explains well how I think of systems differently than others. Essentially, bottom-up thinking is inductive. I see all the details and form patterns that build up the big picture. This is really challenging in the workplace.
Strengths of Bottom-Up Thinking
Bottom-up thinkers often excel at identifying complex patterns that may not be immediately obvious to others. Their tendency to gather comprehensive information before drawing conclusions leads to thorough and nuanced analysis. By approaching problems without preconceived frameworks, they frequently discover novel solutions and make unexpected connections between seemingly unrelated concepts.
3110
Kyle Shevlin (he/him) @kyleshevl.in · 03/02/2026
What do you think y'all? Is this app enough for you?
A div with the id of "app" and the class of ".app" and also a class of "app--app". I have no idea why.
12361
Kyle Shevlin (he/him) @kyleshevl.in · 27/01/2026
Sad day when even the catfishers and bots don’t get my name right.
A chat request from what’s certainly a fake profile that says “hello Kevin”. My name is Kyle, obvs
090
Kyle Shevlin (he/him) @kyleshevl.in · 25/01/2026
It doesn’t have much form, but it functions. Whipped up a French cleat holder for my extra filaments.* *I know they should ideally be stored somewhere that keeps them dry. I’ll look into that eventually.
Two sides that hook into my French cleat wall with dowel rods between each, holding 6 rolls of filament. 2 guitars hand from French cleats below.
2100
Kyle Shevlin (he/him) @kyleshevl.in · 23/01/2026
Don’t think this is supposed to happen
Aloy looking at 3 sun wings sitting in air. Clearly a bug in the video game.
040
Kyle Shevlin (he/him) @kyleshevl.in · 19/01/2026
3d printing custom tools, no matter how small, is cool Also, if you listen closely, you can hear my wife and a friend screaming at Heated Rivalry upstairs.
1160
Kyle Shevlin (he/him) @kyleshevl.in · 18/01/2026
Mask off
Selfie, no mask, still covered in dust, beard matted from the mask, red spot on my nose from the pressure of the mask.
030
Kyle Shevlin (he/him) @kyleshevl.in · 18/01/2026
Sanding drywall fucking sucks.
Selfie, but I have a respirator on and safety goggles, and my entire face mask glasses and hat are covered in drywall dust.
3230
Kyle Shevlin (he/him) @kyleshevl.in · 18/01/2026
I don’t think it’ll get old needing a specific part and being able to model and print it real quick. Needed a vacuum hose adapter to go from my shop vac to the drywall sander I bought. 10 minutes modeling. + 50 mins printing and I have a perfect adapter.
A CAD rendering of the adapter. Basically a tube that changes width. The adapter connecting the hose to the sander
0240
Kyle Shevlin (he/him) @kyleshevl.in · 11/01/2026
Sometimes I wish I had fewer ideas. Execution of an idea is way more important than the quantity of ideas you have; and as someone who struggles with executive function, I get sad when I look at my unmerged branches on my blog.
The output of a git command showing all my unmerged branches and how long its been since I worked on them. One is nearly 5 years old.

  discriminated-unions - 6 months ago - Kyle Shevlin
  prefer-immutable-methods - 11 months ago - Kyle Shevlin
  tree-traversal - 12 months ago - Kyle Shevlin
  optional-chaining-post - 1 year, 1 month ago - Kyle Shevlin
  better-messes - 1 year, 3 months ago - Kyle Shevlin
  analytics-stuff - 1 year, 5 months ago - Kyle Shevlin
  maze-algo-1 - 1 year, 5 months ago - Kyle Shevlin
  twitch-overlays-post - 1 year, 9 months ago - Kyle Shevlin
  further-thoughts-on-devin - 1 year, 10 months ago - Kyle Shevlin
  dueling-pong - 1 year, 11 months ago - Kyle Shevlin
  astro-conversion-post - 2 years ago - Kyle Shevlin
  transducers - 2 years ago - Kyle Shevlin
  guidelines-for-component-based-ui - 2 years, 2 months ago - Kyle Shevlin
  merge-sort - 2 years, 11 months ago - Kyle Shevlin
  upgrade-the-rest - 3 years ago - Kyle Shevlin
  mutation-not-a-sin - 4 years, 1 month ago - Kyle Shevlin
  functions-are-lazy - 4 years, 4 months ago - Kyle Shevlin
  component-composition - 4 years, 8 months ago - Kyle Shevlin
  how-to-generate-a-maze - 4 years, 9 months ago - Kyle Shevlin
2160
Kyle Shevlin (he/him) @kyleshevl.in · 11/01/2026
3D printing is cool. I printed this featherboard for about $3 of filament and $1.50 of hardware. It’s sick!
A spiral featherboard on my table saw. A featherboard is a device with flexible fins that are designed to keep side pressure on a piece of wood as it runs through the blade.
0170
Kyle Shevlin (he/him) @kyleshevl.in · 05/01/2026
In Gleam, there are no loops, which can be intimidating, but not if you get comfortable with recursion. Especially iterative recursion. I first learned there were different types of recursion (linear, iterative, tree) while reading SICP a few years ago. Blew my mind 🤯 Here's an example in JS.
// Linear recursion - build a stack of calls
function map(items, fn) {
  if (items.length === 0) return []

  const [first, ...rest] = items
  
  // Notice we can't return the result of this function
  // without the result of another map function call
  // That's the stacking. Takes up a lot of memory.
  return [fn(first), ...map(rest, fn)]
}



// Iterative - use a helper function with accumulator
function map(items, fn) {
  return mapLoop(items, fn, [])
}

function mapLoop(items, fn, acc) {
  if (items.length === 0) return acc
  
  const [first, ...rest] = items
  
  // Notice we're just calling the function again with
  // new values. We're not waiting on its result to help
  // formulate the result of this function call.
  // Takes up (roughly) the same amount of memory no matter how many calls
  return mapLoop(rest, fn, [...acc, fn(item)])  
}
2180
Kyle Shevlin (he/him) @kyleshevl.in · 05/01/2026
Learning Gleam has been a lot of fun. It's been nice to feel like I'm learning new concepts and expanding my brain a bit. Not something I feel a lot in my day-to-day work. Been learning by doing the lessons/exercises on this platform called Exercism. Made a lot of progress so far.
A screen shot of my progress through the Exercism Gleam course. I'm 49.2% of the way through.
1120
Kyle Shevlin (he/him) @kyleshevl.in · 31/12/2025
With a few days off, I’m spending time on side projects and house projects. Putting in this nickel gap paneling around the lower half of the renovation. Gonna look great when it’s trimmed up and painted.
Paneling with nickel gaps running vertically around an inside corner in an unfinished room.
070
Kyle Shevlin (he/him) @kyleshevl.in · 27/12/2025
Part of the fun of 3D printing is getting to make whatever you can think up pretty quickly. We’ve been keeping our coffee filters in the box they came in. Spent about 20 minutes designing a holder for them and an 1.5hrs later, here it is. Just another small thing making my life a bit better.
A plastic container the perfect shape for holding those wide angular basket coffee filters.
1220
Kyle Shevlin (he/him) @kyleshevl.in · 26/12/2025
Learned more about my dryer today than I ever wanted to
My dryer fully open, drum exposed,
2240
Kyle Shevlin (he/him) @kyleshevl.in · 26/12/2025
LEDs next, then we find it a home. Probably means either some woodworking or 3D printing is in my future for that 🤔
The LEGO Tallneck fully constructed.
2290
Kyle Shevlin (he/him) @kyleshevl.in · 25/12/2025
I don’t think I’ve done Lego in 30 years. And it comes with an LED kit for this model.
A Lego Tallneck from Horizon Zero Dawn and a lighting kit next to it
4391
Kyle Shevlin (he/him) @kyleshevl.in · 22/12/2025
A random thing that gives me joy is when you start to be able to implement methods on a class by using methods from that class over and over. Most of the types I've been working on the last few days, like Maybe, have a `match` method and you can implement most of the other methods with it.
class Maybe<T> {
  /**
   * We won't go over the full implementation, but basically Maybe holds
   * an optional value. If it's not null or undefined, it's a Just, otherwise
   * it's a Nothing.
   *
   * The way we do something with the value is to pattern match. If it's Nothing,
   * do this function, if it's Just, do something else with the privately held value.
   */
  match<J, N>(
    { Just, Nothing }: { Just: (value: T) => J, Nothing: () => N }
  ): J | N {
    // You can imagine how `isJust` is implemented
    return this.isJust() ? Just(this.#value) : Nothing()
  }
  
  // But then you get to start implementing methods by using `match`, like `map`
  map<U>(fn: (value: T) => U): Maybe<U> {
    return this.match({
      Just: value => Maybe.of(fn(value)),
      Nothing: () => Maybe.Nothing()
    })
  }
  
  // Or filter
  filter(predicate: (value: T) => boolean): Maybe<T> {
    return this.match({
      Just: value => (predicate(value) ? this : Maybe.Nothing()),
      Nothing: () => this
    })
  }
  
  // or something that unwraps the value with a default
  getOrElse<U>(defaultValue: U): T | U {
    return this.match({
      Just: x => x // notice this is the identity function,
      Nothing: () => defaultValue
    })
  }
}
030
Kyle Shevlin (he/him) @kyleshevl.in · 22/12/2025
But when it clicks that the state of an async process and the RESULT (pun intended) of that process are actually unrelated and we can compose them, that's where we can easily handle all possible states, and frankly, not have to reset it up, ever again.
A closer look at the code from the full example a few posts prior. Shows how the then of the promise we setState to AsyncData.Done(Result.Ok(data)) and similar in the catch, Async.Done(Result.Err(error))

Then shows how using pattern matching makes it easy to know which state results in what.
010
Kyle Shevlin (he/him) @kyleshevl.in · 22/12/2025
We could try and adapt our state to use a discriminated union to give the Done state a value, but then we'd still have to do the manual work of narrowing a state that is done to whether it's `User | Error`
type State<T> = { status: 'not-asked' } | { status: 'loading' } | { status: 'done', value: T }
110
Kyle Shevlin (he/him) @kyleshevl.in · 22/12/2025
I've often used a string union of enumerated states to make something similar to AsyncData. type State = 'idle' | 'loading' | 'success | 'failure' but you still need other states to hold the data and the error.
The union mentioned in post + three setStates, one to hold the union and two others to hold the data and the error
110
Kyle Shevlin (he/him) @kyleshevl.in · 22/12/2025
I don't know how to do it yet, but I'll figure out how to tell you that this is actually really nice. (i mean I'll do some of it here in a thread, but I'll write a post about it too)
import { AsyncData, Result } from 'some-fp-lib'

function UserProfile({ id }) {
  const [state, setState] = useState<AsyncData<Result<User, Error>>(
    AsyncData.NotStarted()
  )
  
  useEffect(() => {
    function fetchUser() {
      setState(AsyncData.Loading())
     
      fetch(`/api/users/${id}`)
      	.then(res => res.json())
      	.then(data => {
          setState(AsyncData.Done(Result.Ok(data))))
      	})
        .catch(err => {
          setState(AsyncData.Done(Result.Err(err))))
      	})
    }
    
    fetchUser()
  }, [id])
  
  return state.match({
    NotAsked: () => null,
    Loading: () => <div>Loading...</div>
    Done: (result) => result.match({
      Ok: data => <div>{JSON.stringify(data)}</div>
      Err: error => <div>{error.message}</div>                           
    })
  })
}
110
Kyle Shevlin (he/him) @kyleshevl.in · 21/12/2025
I'm spending some time writing a little FP lib for myself as a fun exercise and to learn. While writing it, I came across a TypeScript feature I had never run into before: the `this` parameter. It turns out, you can specify the type that `this` should be when a method is executing.
a flatten() method on a Maybe class. The code says:

flatten<U>(this: Maybe<Maybe<U>>): Maybe<U> {
  return this.flatMap(identity)
}
3140
Kyle Shevlin (he/him) @kyleshevl.in · 18/12/2025
Making some significant progress with learning 3D modeling. Created this phone/tablet holder for my desk shelf in under an hour. Print came out great and works like a charm.
a 3d model of a phone or tablet holder that hooks onto the side of a shelf. It's angled at 20 degrees to give the phone a nice tilt so it can be easily viewed while resting. it has a big hole in the back to reduce the necessary material for print and a hole in the bottom of the base to allow a cord to pass thru for charging.
4120