Dave RupertTop Front-end Bloggersread at source
Conventional Commit rules everything around me
There’s a community standard out there called Conventional Commit for structured git-commit messages. If you don’t know about it already, it recommends a strict type(scope?): description format for describing a code change in a repo. A normal git commit history might look like this: > git log --oneline 9d30ff6 fix typo 3062496 oops d0b38ad shit b5f8df7 redesign site That’s all well and good, but lacks a bit of structure and detail for a big project with lots of contributors. That’s where Conventional Commit comes in. Instead of a git-commit message like add login page , it might look like this: feat: add user login endpoint Or you can “scope” it to a particular section of your application: feat(auth): add user login endpoint There’s even a ! flag to signal a breaking change to your coworkers: feat!: remove all authentication API endpoints The predefined conventional commit types cover most types of changes: fix: , feat: , chore: , docs: , style: , refactor: , perf: , test: , build: , and ci: . I find these cover most situations but I’m also a bad boy and like to break rules, so I’ll throw in custom types like wip: , a11y: , hotfix: , or an rm: (as a treat) when I feel it adds clarity to the work. But why would anyone voluntarily conform to a standard like this? My answer is… a feeling . Look at our git-log history now: > git log --oneline 9d30ff6 fix(input): restore focus state on input 3062496 style(checkbox): update tokens to tokens v2 d0b38ad chore: update dependencies via dependabot b5f8df7 feat(auth): add user login endpoint ... In a noisy repo that’s increasingly getting noisier (thanks, robots) anywhere you can add predictability is a net-positive. The cumulative effect over time is that you have a highly-scannable, uniform history of what’s happening in your project. Conventional Commit can even help with code review. A style: fix that adds dozens of npm packages should be a signal that you need to break up the work. Types might also inform if you route all a11y: commits to the local expert for code review. And a refactor! breaking change should signal coworkers to be extra-cautious in review and may trigger other process-oriented workflows like broadcasting the upcoming breaking change to anyone in the blast radius. And because it’s a community standard, like the Custom Element Manifest , you can build tooling around it like commitlint . As its name implies, commitlint is a linter that gates all commit messages from your entire team to ensure they abide in the type(scope): description format. I personally tend to avoid adding extra tooling to enforce simple conventions, but I might change my mind on this because the benefits start compounding. If my commits were all similarly formatted, I could also automate our entire changelog and semantic versioning bumps for releases. fix: = PATCH feat: = MINOR any!: = MAJOR Of course, Conventional Commit and Semver itself aren’t perfect but it adds a degree of predictability about the impact of a change from the description alone. Abiding in the Conventional Commit format is a small micro-adjustment to my process, but it has potential to positively impact the entire project and I like those sorts of zero-dollar improvements. Extending Conventional Commit beyond commits One place you’ll also want to extend conventional commit to is pull request titles . Not technically a git-commit message, but if you squash and merge commits the PR title tends to become the commit message by default. Preferring or enforcing conventional commit in pull request titles helps preserve our pristine commit history. If you’re doing pull request titles, why not use them for issue titles too: #123 > bug(input): required focus indcator missing #122 > feat(checkbox): update tokens to v2 #112 > chore: update dependencies from depdendabot #106 > feat(input): make input more engaging There’s harmony when the issue and the solution are matching. Having the type in the issue title sets a clear deliverable; a bug: needs a fix: to resolve it. Consider these duplicate issues for the same bug: #189 > 4.2.7 Focus Visible [bug] [components] [severity:HIGH] #210 > bug(input): focus indicator is missing Which one would get picked up first? I’d argue that even with labels like [bug] and [severity:HIGH] that the second one would be easier to fix because it gives the developer almost all the information they need to fix the issue. It makes the issue a little more actionable and that’s what you want. If every issue is an investigation, nothing gets done. Triaging issues takes cognitive bandwidth . Labels are great at grouping and providing queryable views, but when scanning a list of issues pairing both the tag and the title to determine the ask adds an extra cognitive tax. I’m projecting here, but uniform data in your PRs and issue queues could also allow you to collect stats on which components or areas of your application are the most buggy and expensive to maintain. It can tell you who’s getting saddled with all the bugs or which issue filer is adding too much feature debt. And it can help your team quantify your recurring maintenance tasks that might be hiding because it gets rolled into other work. That might be valuable to an organization. Unconventional usage of Conventional Commit Now let’s extend beyond the practical. I’m finding the Conventional Commit format to be so useful, I’ve started rolling them out to a lot of different places (surprise, coworkers!). One example is our folder full of random scripts/ in the monorepo. What was a randomized list of dozens of scripts and tests… scripts/ ├── docs-tokens.mjs ├── playwright-cli.mjs ├── storybook-compat-table.mjs ├── style-dictionary.mjs ├── sync-fluent-fluent-custom-elements-manifest.mjs ├── validate-themes.mjs └── visual-review-harness.mjs Can become an organized paradise of co-located responsibility: scripts/ ├── build/ │ └── themes-style-dictionary.mjs ├── docs/ │ ├── storybook-generate-compat-table.mjs │ ├── tokens-generate-table.mjs │ └── sync-fluent-custom-elements-manifest.mjs └── test/ ├── playwright-cli.mjs └── playwright-visual-review-harness.mjs └── themes-validator.mjs I’ve found those Conventional Commit types tend to map well to areas of responsibility for repo-level scripts. Manage docs, tests, build tasks, and so on all fit under the Conventional Commit umbrella and it adds some much needed organization to the junk drawer of copy-pasta and generated helper scripts. And if your scripts/ folder is following the Conventional Commit format… why not roll that out to your scripts in package.json : { "scripts": { "docs:generate": "node ./scripts/docs/generate-storybook-compat-table.mjs && node ./scripts/docs/generate-storybook-tokens-table.mjs", "build:themes": "node ./scripts/build/themes-style-dictionary.mjs", "test:themes": "node ./scripts/test/themes-validator.mjs" } } Built-in npm features like pre* and post* hooks mess up our perfect naming system a bit. A prebuild:themes task reads fine but predocs:generate is a bit marble-mouthy. But that’s an eye-twitching discrepancy I’m willing to accept to abide in what npm gives me for free. Then… if you’ve added Conventional Commit to your scripts/ and your "scripts" and are living that agentic coding life, why not apply the same organization principle to your skills/ folder? skills/ ├── build/ │ ├── themes/SKILL.md ├── docs/ │ ├── component-jsdoc/SKILL.md │ ├── storybook/SKILL.md │ └── technical-writing/SKILL.md └── test/ ├── playwright-tests/SKILL.md ├── visual-tests/SKILL.md └── writing-tests/SKILL.md This looks great… it falls apart in the real world. Skills installed from the internet don’t abide in this format. Having some skills nested and others not creates a feeling of unease, so a flatter folder structure for skills is probably better. That said, it’s not uncommon to need a skill to inform your model about your repo-level scripts and those are places you can still maintain parity. skills/ ├── build-themes/SKILL.md ├── docs-storybook/SKILL.md ├── docs-components/SKILL.md ├── docs-technical-writing/SKILL.md ├── grill-me/SKILL.md ├── i-have-adhd/SKILL.md ├── test-playwright/SKILL.md ├── test-visual-review/SKILL.md └── test-writing/SKILL.md While applying Conventional Commit to your skills/ folder is a bit impractical, one area I can confirm it does help is organizing your agent chats. Fire off a handful of agents and it can get confusing about which one is doing what task, even with their autogenerated chat label. I’ve found it super helpful to ask your bot to rename its chat using the Conventional Commit format. Try it out and bask in the uniform list of tasks. To get this to work consistently in the GitHub Copilot app, I had to modify my session prompt in Sessions > App instructions: At the start of every new chat, before answering my first request, call `rename_session` using `type(scope): description`. Do this even for non-coding chats. Rename only once unless I ask otherwise. I’m not sure how it works in your preferred agent manager, but I assume you could write a skill or put it in your AGENTS.md , but I’d have a conversation with your coworkers first before codifying your obsessive-compulsions in the project repo. Join me in my obsession You might have read all this and said, “This is a man who –in a world of chaos– is looking to over-manage a small thing like title text so that he can feel in control of his own life!” and to that I’d say… Probably. I like to think I’ve just hit the point in my life where I don’t want to spend an single brain cell thinking about how I should format title text or name a file. I wasn’t this passionate about Conventional Commit a year ago, I think I got radicalized. Generative AI almost certainly plays a big part where every robot names files and fixes different every time. That’s built-in to its dice-roll mechanic. Knowing there’s a chaos agent in the mix, I feel a need to find low-hanging fruit for consistency and (for right now) this feels pretty good to me.
Dave RupertTop Front-end Bloggersread at source
Pseudo-pseudo-classes for maximum interactive demos!
Have you ever wanted to display all variants of a Button next to each other in your Storybook like your designer did in Figma? But then you come to a screeching halt when you realize :hover , :active , :focus don’t work without user-interaction? Blergh. Whatever can we do? I got nerd-sniped by this problem in the ShopTalk Discord when Nils said he was trying to build a simple state machine demo for his components. A handful of immediate options came to my head: Style states using .is-hover utility classes (à la SMACSS ) Create a system of [data-state="hover"] attributes and copy over styles Build some over-the-top Playwright-based screenshot contraption Each of those approaches has trade-offs for me. I don’t want to ship extra component styles or manage duplicate global styles. And if I’m building a Playwright contraption for a CSS demo… yeesh, I should probably back away from the keyboard. When Nils mentioned he was using web components, I realized I might have an ace up my sleeve for a simple’ish solution to this problem. See the Pen Pseudo-pseudo-states Demo! by Dave Rupert ( @davatron5000 ) on CodePen . There’s a handful of Advanced Web Component Tips & Tactics happening in this demo like adoptedStyleSheets , ElementInternals , and custom state sets but the approach is pretty lightweight. I scan the custom element’s styles, clone the interactive bits, rewrite styles like :hover state to :state(hover) and add the new stylesheet inside the component. What I like about this approach compared to the other ideas I had is: Styles owned by the component - No “magic” global styles or utilities. Appended styles also owned by the component - No global conflicts. Interactive states use regular HTML/CSS - Avoid managing interactive state in JavaScript when possible. Write once, ship once - I don’t write or ship duplicate :hover and .is-hover styles to customers. Low maintenance - Beyond the initial setup, it’s automatic and I’m resilient to component style updates. Want an end-to-end walkthrough of how it all works? Please, follow me… Adopting stylesheets in your web components In web components, we’d typically author our styles in a CSS tagged template literal: const MyButtonStyles = css` /* Change the host-element itself */ :host(:hover) { background: pink; scale: 1.1; } /* Update a child element based on the host-element interaction */ :host(:hover) button { background: pink; scale: 1.1; } /* Or put the interaction on the interactive child-element */ button:hover { background: pink; scale: 1.1; } ` Then we create a new “constructed stylesheet” and populate the stylesheet with the styles we wrote. That stylesheet then attaches to our component by adding it to the Shadow Root’s array of adoptedStyleSheets . /* Create a new constructed stylesheet */ const MyButtonStyleSheet = new CSSStyleSheet() MyButtonStyleSheet.replaceSync(MyButtonStyles) class MyButton extends HTMLElement { constructor() { /* Setup and create shadowroot */ super(); this.attachShadow({ mode: "open" }) /* Adopt the constructed stylesheet */ this.shadowRoot.adoptedStyleSheets = [MyButtonStyleSheet] /* ... etc */ } } The component will now render its styles without a tag or inline tag in the template. If we put two MyButton components on the page, they’ll share the same stylesheet in memory which is a pretty neat efficiency gain for style application. adoptedStyleSheets aren’t limited to web components, but they’re still pretty niche. But the web component ecosystem’s pattern of using adoptedStyleSheets for style application will come in handy later when we want to apply our new custom states. Attach internals to unlock custom states Before we jump to the grand finale, there’s one other unique-to-web-components feature we need to talk about first: ElementInternals . In a web component class you can call attachInternals() which allows you to dip into the element’s guts and mutate the innards of your spicy div . An example of this might be setting an ARIA role: class MyButton extends HTMLElement { constructor() { super(); this._internals = this.attachInternals() this._internals.role = "button" } } customElements.define('my-button', MyButton) The accessibility inspector will report a role of button for your custom element. Setting ARIA role is a weak example because both and Element.role = "button" both work pretty seamlessly. But ElementInternals brings other ✨superpowers✨ like adding form validation behaviors that you can’t do on a normal div . One newer capability of ElementInternals is that it allows you to create your own custom pseudo-classes and apply styles using :state() . class MyScrollingElement extends HTMLElement { constructor() { super(); this._internals = this.attachInternals() } connectedCallback() { this.addEventListener('scroll', this.handleScroll) this.addEventListener('scrollend', this.handleScrollEnd) } /* Use arrow functions to avoid `this` complications */ handleScroll = (_event) => { this._internals.states.add('scrolling') } handleScrollEnd = (_event) => { this._internals.states.delete('scrolling') } } Now you can style your element when it’s scrolling using :state(scrolling) and you’re manipulating the element’s internal state, not modifying HTML in the DOM. :host { overflow-y: scroll; transition: background 0.2s; } :host(:state(scrolling)) { background: pink; } Isn’t that cool? With great power comes great spider-mans, of course, but having your own custom pseudos is a thing you can do now with web components. Mimic native pseudo-states using custom states Now that we have a loose grasp on all the pieces we need to make our demo work, the next steps are: Find all our components and loop through them Copy their interactive rules to a new constructed stylesheet Replace :hover with :state(hover) Adopt the constructed stylesheet in the component Attach internals and imperatively set the state Here’s a simplified version of what’s happening in the CodePen version. /* A bit of pseudo-code here... */ const MyButtons = document.querySelectorAll('my-button'); const sheet = new CSSStyleSheet(); MyButtons.forEach(async (button, index) => { const isDefined = await customElements.whenDefined(button.localName); // Grab the stylesheet from the first if(isDefined && index === 0) { // Get current CSS rules const cssRules = Array.from( button.shadowRoot.adoptedStyleSheets[0].cssRules ) .filter(rule => rule.cssText.includes(':hover')) .map(rule => rule.cssText.replaceAll(':hover', ':state(hover)')); // Update the stylesheet contents sheet.replaceSync(cssRules.join('')); } // Adopt the stylesheet button.shadowRoot.adoptedStyleSheets = [...button.shadowRoot.adoptedStyleSheets, sheet] // Attach internals and set the state if(button.dataset.state == 'hover') { const _internals = button._internals ?? button.attachInternals(); _internals.states.add('hover'); } }) And that’s the gist of the trick: clone the pseudo-class states and set imposter custom states. I’m hand-waving some of the more complex bits like button:hover {} needs to be rewritten to :host(:state(hover)) button {} , but again, feel free to read the code. And my demo makes a couple assumptions I know won’t scale: All elements use the same static stylesheet (e.g. it doesn’t change per component). Each only has one adoptedStyleSheet. Hard-coded to HTMLButtonElement -specific interactions. Assumes you either don’t have ElementInternals already setup or already externally expose them and you know that @public property name. Next steps might be to make this a bit more robust and support all the other platform pseudo-classes like :popover-open , :user-invalid , or :playing . An agent could probably do this, not me though. I’m tired. Shadow DOM isn’t that bad, actually. Shadow DOM often gets a bad rap even in web component circles. I fully understand the friction there. Everyone’s first test drive of Shadow DOM styling almost always ends up in flames, leading to a poor unboxing experience that’s hard to shake off. That said, I feel like this demo captures a glimpse at some of the unique ✨superpowers✨ you get when styling with Shadow DOM. When coming from a global styles world, that loss of control can feel frustrating. But in my experience, with Shadow DOM encapsulation you’re gaining predictability and precise control by keeping everything local. You could probably figure out a way to do this with global styles. It’d be more difficult with roboclass-based scoping from something like CSS Modules, but I think it’s possible. But I imagine you’d have to parse through all the global document.styleSheets and that seems like a lot of work to add a :hover state demo to a . Style encapsulation here saves a lot of work. If you got all the way here and still hate Shadow DOM, I’d love to read a post of yours about where it broke down for you. But if you use Shadow DOM on the regular, I’d love to hear why it’s great. This has been my mantra for awhile now, but I think the web component community can do more about promoting the benefits. For example, there’s only two articles about adoptedStyleSheets on CSS-Tricks which seems low. In the meantime, while I try to convince my bosses to let me blog for a living, I’ve got my free course guidebook online and you can check it out.
Dave RupertTop Front-end Bloggersread at source
Write mo'
I decided not to do #NaNoWriMo this year. Part of that is because I’ve never done it. Never even attempted it. Don’t even want to. Writing a novel is a life goal of mine, but cramming it all into one month isn’t for me. November is impossibly busy. But I do think it would be cool to have a big excuse for avoiding all those obligations. “Dave, come to this holiday party!” “Sorry, I’m writing a novel.” “Dave, come work at the school’s fall carnival!” “Sorry, I’m writing a novel.” “Dave, pay attention to your kids!” “Sorry, I’m writing a novel.” See, that level of self-preservation and pre-emptive boxing out of other responsibilities would be so cool. Like cigarettes cool. Despite not having a novel excuse, I am feeling compelled to wri’ mo’ this month. Whether it’s autumnal ghosts or pumpkin spices, there’s a draw pulling me back to my text editor. A vapor of creativity stolen by corporate dementors and their infernal machines, seeping back into my lifeless shell. Last week I powered through a handful of short stories by Arthur C. Clarke. Vintage sci-fi inspires me. Partly because the experimental short form factor containing a single “ What If? ” thought experiment seems achievable and because most of the stories are complete shit. In a modern world of blockbuster movie adaptations and eight volume octologies, vintage sci-fi sets the bar real low. One story I came across was The Nine Billion Names of God . The audiobook has a sick-ass cover of a skeleton in a cracked space helmet on a dying planet with a giant sun rising in the distance. You can pause this and get through it in ten minutes. Nothing in the story relates to that cover. Rather, the story is about two Western men that work for a fancy computer company that makes the Mark V . They’re hired by a group of Tibetan monks to help them write a program that will procedurally generate the nine billion names of God which they believe will bring about the end of the world. The Mark V will shorten the task of writing down all the possible names of God from 15,000 years of manual writing down to 100 days! Incredible productivity gains! AAABBBAAA AAABBBABA AAABBBACA AAABBBADA AAABBBAEA AAABBBAFA AAABBBAGA AAABBBAHA AAABBBAIA AAABBBAJA AAABBBAAB AAABBBABB AAABBBACB AAABBBADB AAABBBAEB AAABBBAFB AAABBBAGB AAABBBAHB AAABBBAIB AAABBBAJB AAABBBAAC AAABBBABC AAABBBACC AAABBBADC AAABBBAEC AAABBBAFC AAABBBAGC AAABBBAHC AAABBBAIC AAABBBAJC AAABBBAAD AAABBBABD AAABBBACD AAABBBADD AAABBBAED AAABBBAFD AAABBBAGD AAABBBAHD AAABBBAID AAABBBAJD AAABBBAAE AAABBBABE AAABBBACE AAABBBADE AAABBBAEE AAABBBAFE AAABBBAGE AAABBBAHE AAABBBAIE AAABBBAJE AAABBBAAF AAABBBABF AAABBBACF AAABBBADF AAABBBAEF AAABBBAFF AAABBBAGF AAABBBAHF AAABBBAIF AAABBBAJF AAABBBAAG AAABBBABG AAABBBACG AAABBBADG AAABBBAEG AAABBBAFG AAABBBAGG AAABBBAHG AAABBBAIG AAABBBAJG ... ... ... ... ... ... ... ... ... ... THIS STORY HAS NO MODERN DAY PARALLELS. An altruistic sex cult from a specific locale using a mountain of money to hasten the end of the world with their enormous computers that endlessly generate combinations of nonsense words that they believe will summon God… well, that would be a work of cheesy science fiction, not real life. The Nine Billion Names of God flipped a switch in me and has renewed my courage to write more. As someone with ideas about ideas burning holes in my head, it’d be worthwhile to offload some of my cognitive surplus. I don’t have any solid plans, words-per-day goals, charts, graphs, storyboards, outlines, or some Benjamin Franklin life hack schedule to make this happen. Instead I have an Obsidian vault with a hundred unsorted drafts. I don’t want to commit myself to publishing every day, but my goal is to get up and spend thirty minutes to an hour each day writing something . I want to carve out time before work, before doing my puzzles, before doom-scrolling the news. I want to use those premium dopamine reserves –the flavorful floating creme of consciousness that comes from a good night’s sleep, not the metallic bitter dregs distilled from caffeine and stimulants– I want to savor and use that precious natural dopamine on writing. My promise to you, dear reader, is that I will write more this month. I will write no less than nine billion stories and/or blog posts by the end of November. And upon completing my works, I can shed this mortal coil and leave this forsaken world.
Dave RupertTop Front-end Bloggersread at source
Arc-like automatic picture-in-picture in Chromium
There’s a lot to miss about Arc (RIP), but the feature I miss the most has got to be automatic picture-in-picture. You start a video and when you change tabs to jump to another task, the video anchored itself to the corner of your desktop. Go back to the video tab and it slurps up into the video player area. A perfect experience with no interruption. I switched back to Edge (for work) and missed Auto-PiP a lot. I tried building a custom browser extension to restore the functionality myself but hit a platform limitation with the mix of WebExtensions , PageVisibility , and MediaSession APIs you need to get it to work seamlessly. You had to configure each site in a dropdown and run a script each MediaSession… it asked a lot of the user. There’s been a handful of Chromium flags and experiments to add “Auto-PiP” over the years, but nothing did it as well as Arc did… until… about://flags#browser-initiated-automatic-picture-in-picture Chrome 142 introduced a new browser-initiated-automatic-picture-in-picture flag and it behaves the same as Arc. The browser does all the work popping the video in-and-out when expected. I couldn’t believe this near-exact replica was hiding in plain sight all this time! But I couldn’t use it because the flag didn’t exist in Edge 😭 I started up a couple internal threads about it last year but it went nowhere. When the topic came up again this year, I mentioned to Greg Whitworth , “Y’know there’s a flag in Chrome that does Auto-PiP great…” and that next week the flag showed up in Canary and I’m happy to say it’s available today in mainline Edge 156. If you missed that feature in Arc and didn’t know it existed in Chrome or felt crushed by it not existing in Edge… then head over and turn on the flag today, I think you’ll be happy. The only thing it’s missing for me are the juicy physics-based animations that let you two-finger swipe and throw the picture-in-picture window around and pinch-zoom to resize. But it feels like the bones are there. And it feels like a good time to remind my fellow Edge users that Edge has a little feedback form inside the Three Dots menu > Help and Feedback > Send Feedback.
Dave RupertTop Front-end Bloggersread at source
Bad at email
Good friends and family know this, but I am bad at email. Like chronically bad. If you have tried to email me, you know this too and you either never got a reply or received one 9-months later starting with the phrase, “Sorry.” I am truly sorry… but also I’m not. It’s a function of who I am. I was never great at email, but in a slower time before social media it raptured my attention. I would habitually refresh my inbox hoping for a long range communication from a friend or stranger. When I was homesick in Japan, a good email (or AIM chat) could turn my day around. I have fond memories of email and its unparalleled ability to connect me to other people through a single address. It of course would never be as familiar or intimate as scribing a hand-written calligraphic letter with a big feather pen, but it proved a fine substitute. At Paravel, I noticed that emails had a propensity to crash into my day with some new urgent task. A ding, a red badge, a bouncing icon in the dock . Could be a client or my accountant; something that required me to stop what I was doing and switch over to their need. Frank once described emails as (paraphrasing) “a to-do you send to someone else” and that resonated in my soul. For someone with an attention regulation disorder, intermittent distractions like email (or chats) can be devastating to staying focused on the work I actually need to finish. Email being disruptive to your productivity must be a pretty common phenomenon because nearly all the productivity books out there –and I’ve read them all– attempt to “solve” productivity by turning off your notifications and checking your email less. I must have really taken this advice to heart because I almost never check my email anymore. After a long day of expending dopamine on work, the last thing I want to do at home is check my personal email and receive more to-dos from strangers. I tend to check my personal email now once a week… or less. Inbox Zero is a fool’s errand for me, but I try to keep my work email groomed and as close to zero as possible. Part of that was because I started with a blank slate and could actually succeed. Each morning I make an analog to-do for myself to check my work email. I set up some priority inboxing and auto-deletes for org-level notifications. Importantly, work email stays sequestered to my work laptop. But despite the success at work, I’m sitting at nearly 500+ unreads in my personal Inbox. It doesn’t feel great and feels like a monumental task now. Since starting at Large Company I know of at least two conferences that I’ve missed out on speaking at because I’m so bad at email (sorry, PPK ). If you’ve ever wondered why I don’t have a contact form on my site or do that cool “email me, let’s be friends” link in my RSS footer, this is why… because I feel shame that I will disappoint you . There’s also the diminishing utility of email. As with phone calls and texts, the amount of spam and noise has ruined the service. It’s a needle in a haystack to find the one good email that actually deserves your attention. Just because we participated in online commerce once doesn’t mean you get the liberty to email me for the rest of my life. I wish there were more laws about this. When did a ten drip email campaign for every online transaction become the standard? You bought an item → Item is getting ready to ship → Want another item? → Item shipped → Need more items? → Item out for delivery → The holidays are coming up → Package delivered → Let us know how we did → Mother’s day coming up → Father’s day coming up → Step-father’s day coming up → etc… It’s entirely too much. And the grifter slop… oh, my God the grifter slop! Hey Dave, I came across your now-defunct company Luro. Design systems is an interesting space. Would you like to increase Luro users by six billion percent through spam email blasts? We can help. Chad, CEO I will never reply to this and– Dave, I thought I’d follow up in case you missed the last email. I’m a fucking robot obviously but we can have a conversation about this. Chad, CEO Grifters, spend six minutes crafting a real email and I promise you it will convert better. Or have an actual fucking product that is so good I hear about it from other people. You are one of five hundred anonymous Chads in my inbox, this pickup line doesn’t work anymore. Along those lines… Democrats, sending text messages doesn’t work anymore. I got AOC, Nancy Pelosi, Beto, and even Raphael Warnock’s grandma texting me every other day and it makes you look broke and desperate like a friend I’m gonna need to bail out of jail again. Capture me with ideas. That’s how I feel about email. And text messages. And phone calls. And social media. Once upon a time it had utility, now it’s all noise and grifter slop demanding our attention and giving us to-dos. Be hotter, be smarter, be more liberal, be more productive, be more, be more, be more. Oh, and I underpaid my taxes and owe the government $5400. That was the email I missed.