<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Today I Learned</title><description>A collection of useful things I&apos;ve learned.</description><link>https://til.jakelazaroff.com/</link><item><title>Make all rounded corners squircles</title><link>https://til.jakelazaroff.com/css/make-all-rounded-corners-squircles/</link><guid isPermaLink="true">https://til.jakelazaroff.com/css/make-all-rounded-corners-squircles/</guid><pubDate>Sun, 18 Jan 2026 08:01:44 GMT</pubDate><content:encoded>&lt;p&gt;Have you heard of &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/corner-shape&quot;&gt;&lt;code&gt;corner-shape&lt;/code&gt;&lt;/a&gt;? It&amp;#39;s the new hottest CSS property! You can use it to make corners that are notches, squircles and all sorts of other cool stuff.&lt;/p&gt;
&lt;p&gt;It&amp;#39;s as easy as this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;*,
*::before,
*::after {
  corner-shape: squircle;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It only works in Blink browsers for now, but that&amp;#39;s okay — graceful degradation, right?&lt;/p&gt;
&lt;p&gt;The only &lt;em&gt;real&lt;/em&gt; issue is that, in my experience, the radii of squircle corners tend to appear about half as big as &amp;quot;normal&amp;quot; rounded corners. So I&amp;#39;d put something like this in my CSS reset:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;:root {
  --radius-sm: 4px;
  --radius-md: 6px;
  /* ... */
}

@supports (corner-shape: squircle) {
  *,
  *::before,
  *::after {
     corner-shape: squircle;
  }

  :root {
    --radius-sm: 8px;
    --radius-md: 12px;
    /* ... */
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Define the corner radii design tokens, and then if the browser supports squircle corners, make all corners squircles and double the corner radii.&lt;/p&gt;
</content:encoded><category>css</category></item><item><title>Load an import map from a JSON file</title><link>https://til.jakelazaroff.com/javascript/load-an-import-map-from-a-json-file/</link><guid isPermaLink="true">https://til.jakelazaroff.com/javascript/load-an-import-map-from-a-json-file/</guid><pubDate>Sun, 18 Jan 2026 07:54:35 GMT</pubDate><content:encoded>&lt;p&gt;One of the cooler features of ES modules is &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules#importing_modules_using_import_maps&quot;&gt;import maps&lt;/a&gt;, which let you change JavaScript identifier names so you can import them as though they were Node modules.&lt;/p&gt;
&lt;p&gt;Basically, you put a script tag like this in your HTML, before your module graph loads:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt;
{
  &amp;quot;imports&amp;quot;: {
    &amp;quot;preact&amp;quot;: &amp;quot;https://esm.sh/preact&amp;quot;
  }
}
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then you can write &lt;code&gt;import { h } from &amp;quot;preact&amp;quot;&lt;/code&gt; rather than &lt;code&gt;import { h } from &amp;quot;https://esm.sh/preact&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, the spec doesn&amp;#39;t allow import maps to be loaded remotely — which is to say, you can&amp;#39;t do something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script type=&amp;quot;importmap&amp;quot; src=&amp;quot;/importmap.json&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; create them dynamically, though! Baldur Bjarnason found a way: &lt;a href=&quot;https://www.baldurbjarnason.com/2023/dynamic-import-map/&quot;&gt;use a non-module script to inject a script tag&lt;/a&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script&amp;gt;
  const importMap = {
    // import map goes here
  };

  const importmap = document.createElement(&amp;quot;script&amp;quot;);
  importmap.type = &amp;quot;importmap&amp;quot;;
  importmap.textContent = JSON.stringify(importMap);
  document.currentScript.after(importmap);
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But how to load our JSON file into the script? We can&amp;#39;t &lt;code&gt;import&lt;/code&gt; them — remember, import maps need to load before any modules — and we can&amp;#39;t &lt;code&gt;fetch&lt;/code&gt;, since there&amp;#39;s no way to force the browser to wait for the response before it continues executing other script tags.&lt;/p&gt;
&lt;p&gt;Did you know that &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest_API/Synchronous_and_Asynchronous_Requests&quot;&gt;&lt;code&gt;XMLHttpRequest&lt;/code&gt; supports synchronous requests&lt;/a&gt;? If we pass &lt;code&gt;false&lt;/code&gt; as the third parameter to &lt;code&gt;XMLHttpRequest.open&lt;/code&gt;, it will block until the response comes back — meaning the browser won&amp;#39;t continue executing any other JavaScript.&lt;/p&gt;
&lt;p&gt;Once the response comes back, we can insert the import map script into the DOM and everything is hunky dory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// make a synchronous XMLHttpRequest for the import map
const xhr = new XMLHttpRequest();
xhr.open(&amp;quot;GET&amp;quot;, document.currentScript.dataset.src, false);
xhr.send();

// create a &amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt; with the response text
const importmap = document.createElement(&amp;quot;script&amp;quot;);
importmap.type = &amp;quot;importmap&amp;quot;;
importmap.textContent = xhr.responseText;

// insert the &amp;lt;script type=&amp;quot;importmap&amp;quot;&amp;gt; after the current script
document.currentScript.after(importmap);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You&amp;#39;d use that like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&amp;quot;/importmap.js&amp;quot; data-src=&amp;quot;/importmap.json&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Caveats abound: this will &lt;em&gt;block all rendering&lt;/em&gt; while that &lt;code&gt;XMLHttpRequest&lt;/code&gt; is in flight! I&amp;#39;ve been using it mostly for local development, where latency is low and the chance of a request failing is basically non-existent.&lt;/p&gt;
</content:encoded><category>javascript</category></item><item><title>Select a previous sibling</title><link>https://til.jakelazaroff.com/css/select-a-previous-sibling/</link><guid isPermaLink="true">https://til.jakelazaroff.com/css/select-a-previous-sibling/</guid><pubDate>Wed, 30 Jul 2025 11:20:48 GMT</pubDate><content:encoded>&lt;p&gt;Let&amp;#39;s say we have this markup:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;one&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;two&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;three&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;four&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a long time, we&amp;#39;ve been select an element&amp;#39;s &lt;em&gt;next&lt;/em&gt; siblings:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.one ~ .four {
  /* selects an element with class `four` if preceded by a sibling element with class `one` */
}

.two + .three {
  /* selects an element with class `three` if *immediately* preceded by a sibling element with class `two` */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But we haven&amp;#39;t been able to select an element&amp;#39;s &lt;em&gt;previous&lt;/em&gt; siblings — like, an element with class &lt;code&gt;one&lt;/code&gt; (as long as it&amp;#39;s followed by an element with class &lt;code&gt;four&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Enter the &lt;code&gt;:has&lt;/code&gt; selector! This trick is simple enough that I&amp;#39;m sure it&amp;#39;s written up elsewhere, but here you go:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.one:has(~ .four) {
  /* selects an element with class `one` if followed by a sibling element with class `four` */
}

.two:has(+ .three) {
  /* selects an element with class `two` if *immediately* preceded by a sibling element with class `three` */
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>css</category></item><item><title>Change the navigation bar font</title><link>https://til.jakelazaroff.com/swiftui/change-the-navigation-bar-font/</link><guid isPermaLink="true">https://til.jakelazaroff.com/swiftui/change-the-navigation-bar-font/</guid><pubDate>Sat, 26 Jul 2025 06:01:54 GMT</pubDate><content:encoded>&lt;p&gt;I wanted to set a SwiftUI iOS app in &lt;a href=&quot;https://v-fonts.com/fonts/sf-rounded&quot;&gt;SF Rounded&lt;/a&gt;. It&amp;#39;s a system font, so I thought it&amp;#39;d be pretty easy. On the web, it would take like two lines of CSS.&lt;/p&gt;
&lt;p&gt;Setting the font of the &lt;em&gt;content&lt;/em&gt; to SF Rounded was pretty easy — just call the &lt;code&gt;fontDesign(.rounded)&lt;/code&gt; on the root view:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct myApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .fontDesign(.rounded)
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setting the font of the &lt;em&gt;navigation bar&lt;/em&gt;, though, turned out to be surprisingly hard! I don&amp;#39;t think you can actually do it in SwiftUI alone — you need to pull out some UIKit APIs.&lt;/p&gt;
&lt;p&gt;I asked on &lt;a href=&quot;https://bsky.app/profile/jakelazaroff.com/post/3lun6jp6cx22f&quot;&gt;Bluesky&lt;/a&gt; and got some very nice help from &lt;a href=&quot;https://malstrom.me&quot;&gt;Mark Malstrom&lt;/a&gt;. A lot of resources online seem to recommend similar approaches: creating an instance of &lt;a href=&quot;https://developer.apple.com/documentation/technotes/tn3106-customizing-uinavigationbar-appearance/&quot;&gt;&lt;code&gt;UINavigationBarAppearance&lt;/code&gt;&lt;/a&gt; and setting &lt;code&gt;UINavigationBar.appearance().standardAppearance&lt;/code&gt; to that instance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct myApp: App {
  init() {
    guard let descriptor = UIFontDescriptor
      .preferredFontDescriptor(withTextStyle: .headline)
      .withDesign(.rounded) else {
        return
      }

    let appearance = UINavigationBarAppearance()
    appearance.titleTextAttributes = [
      .font: UIFont(descriptor: descriptor, size: 0)
    ]

    UINavigationBar.appearance().standardAppearance = appearance
  }

  var body: some View {
    // ...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Some resources will also tell you to set the &lt;code&gt;compactAppearance&lt;/code&gt; and &lt;code&gt;scrollEdgeAppearance&lt;/code&gt; properties, but Apple&amp;#39;s documentation says that if those are set to &lt;code&gt;nil&lt;/code&gt; it applies the &lt;code&gt;standardAppearance&lt;/code&gt; in those cases.)&lt;/p&gt;
&lt;p&gt;On the other hand, &lt;a href=&quot;https://stackoverflow.com/a/68418167&quot;&gt;some resources&lt;/a&gt; recommended mutating &lt;code&gt;UINavigationBar.appearance()&lt;/code&gt; directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-swift&quot;&gt;@main
struct myApp: App {
  init() {
    guard let descriptor = UIFontDescriptor
      .preferredFontDescriptor(withTextStyle: .headline)
      .withDesign(.rounded) else {
        return
      }

    UINavigationBar.appearance().titleTextAttributes = [
      .font: UIFont(descriptor: descriptor, size: 0)
    ]
  }

  var body: some View {
    // ...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first seems &lt;a href=&quot;https://developer.apple.com/documentation/technotes/tn3106-customizing-uinavigationbar-appearance/&quot;&gt;better documented&lt;/a&gt;, and Claude tells me that the second is a deprecated way of accomplishing it, so I went with that one.&lt;/p&gt;
</content:encoded><category>swiftui</category></item><item><title>Represent a sum type</title><link>https://til.jakelazaroff.com/go/represent-a-sum-type/</link><guid isPermaLink="true">https://til.jakelazaroff.com/go/represent-a-sum-type/</guid><pubDate>Fri, 04 Jul 2025 10:32:20 GMT</pubDate><content:encoded>&lt;p&gt;Let&amp;#39;s say we have two structs in Go:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Foo struct {
  One string
}

type Bar struct {
  Two int
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we create a sum type — a type that can hold &lt;em&gt;either one&lt;/em&gt; of our structs? Go doesn&amp;#39;t have subtypes (&lt;a href=&quot;https://journal.stuffwithstuff.com/2023/10/19/does-go-have-subtyping/&quot;&gt;or does it?&lt;/a&gt;) but it does have a feature called &lt;em&gt;interfaces&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;An interface describe a set of methods that a type must implement. When a type implements all the methods, we say it &lt;em&gt;satisfies&lt;/em&gt; the interface.&lt;/p&gt;
&lt;p&gt;The trick here is to create an interface with a single stub method (I&amp;#39;ve called it &lt;code&gt;_type&lt;/code&gt; here, but that name is arbitrary). We then implement that stub method with an empty  function for each struct type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Union interface {
  _type()
}

func (u Foo) _type() {}
func (u Bar) _type() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we have a variable that satisfies the interface, we can switch on its underlying type:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;func PrintUnion(unknown Union) {
	switch thing := unknown.(type) {
	case Foo:
		// thing has type Bar in this branch
	  fmt.Printf(&amp;quot;%s\n&amp;quot;, thing.One)
	case Bar:
	  // thing has type Bar in this branch
	  fmt.Printf(&amp;quot;%d\n&amp;quot;, thing.Two)
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This technique was mostly cribbed from &lt;a href=&quot;https://zackoverflow.dev/writing/hacking-go-to-give-it-sumtypes/&quot;&gt;this Zack Radisic blog post about sum types in Go&lt;/a&gt;.&lt;/p&gt;
</content:encoded><category>go</category></item><item><title>Run an HTTPS reverse proxy for local development</title><link>https://til.jakelazaroff.com/caddy/run-an-https-reverse-proxy-for-local-development/</link><guid isPermaLink="true">https://til.jakelazaroff.com/caddy/run-an-https-reverse-proxy-for-local-development/</guid><pubDate>Sat, 17 May 2025 00:40:35 GMT</pubDate><content:encoded>&lt;p&gt;Really the title should be something like &amp;quot;run an SSL-terminating reverse proxy&amp;quot;, but most of the resources on the subject talk about using &amp;quot;HTTPS for local development&amp;quot;, so here we are.&lt;/p&gt;
&lt;p&gt;The standard advice is to use a tool called &lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot;&gt;mkcert&lt;/a&gt;, which installs a root CA on your computer and issues self-signed certificates for local domains. That works, but the drawback is that your app now needs to deal with SSL termination. It&amp;#39;s not the end of the world, but in production SSL termination probably happens at the network layer before your app receives any requests.&lt;/p&gt;
&lt;p&gt;That&amp;#39;s where web-server-Swiss-army-knife Caddy comes in! Rather than using HTTPS for your local development server, have Caddy handle the SSL stuff and proxy the request back to your local development, which (none the wiser) serves plain old unencrypted HTTP.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, &lt;a href=&quot;https://caddyserver.com/docs/quick-starts/reverse-proxy&quot;&gt;Caddy&amp;#39;s documentation&lt;/a&gt; reveals that there&amp;#39;s one-liner to do this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;caddy reverse-proxy --to http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;8080&lt;/code&gt; with whatever your development server&amp;#39;s local port is. Boom: an SSL-terminating reverse proxy (or, if you prefer, &amp;quot;HTTPS&amp;quot;) in local development, with no changes to your code!&lt;/p&gt;
</content:encoded><category>caddy</category></item><item><title>Add a Git hook on Windows</title><link>https://til.jakelazaroff.com/git/add-a-git-hook-on-windows/</link><guid isPermaLink="true">https://til.jakelazaroff.com/git/add-a-git-hook-on-windows/</guid><pubDate>Thu, 15 May 2025 08:35:36 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks&quot;&gt;Git Hooks&lt;/a&gt; are scripts that Git runs for you when certain actions occur.&lt;/p&gt;
&lt;p&gt;At root of every repo, there&amp;#39;s a &lt;code&gt;.git&lt;/code&gt; directory. Inside, there&amp;#39;s a folder called &lt;code&gt;hooks&lt;/code&gt; that contains a bunch of files named things like &lt;code&gt;pre-commit.sample&lt;/code&gt;. Each of those files is an example of a Git Hook. To create one for &lt;em&gt;real&lt;/em&gt;, create a file of the same name without the extension, and Git will automatically run the script for you at the appropriate time.&lt;/p&gt;
&lt;p&gt;At least, that&amp;#39;s what I thought would happen when I tried adding this pre-commit hook on Windows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;npm run typecheck
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I tried to commit it, though, I kept getting this error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;#39;pre-commit&amp;#39; is not recognized as an internal or external command,
operable program or batch file.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried running &lt;code&gt;attrib +x .git\hooks\pre-commit&lt;/code&gt;, changing the extension to &lt;code&gt;.bat&lt;/code&gt; and a bunch of other things that didn&amp;#39;t work before stumbling upon this StackOverflow answer that held the key: &lt;a href=&quot;https://stackoverflow.com/a/51005120&quot;&gt;Git for Windows has an embedded bash shell&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The fix turned out to be just adding a hashbang to the top of the script and write it as though it were a Unix shell script: &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;#!/bin/sh
npm run typecheck
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>git</category></item><item><title>Create a file without an extension</title><link>https://til.jakelazaroff.com/windows/create-a-file-without-an-extension/</link><guid isPermaLink="true">https://til.jakelazaroff.com/windows/create-a-file-without-an-extension/</guid><pubDate>Fri, 25 Apr 2025 10:20:29 GMT</pubDate><content:encoded>&lt;p&gt;I was trying to save a file without an extension from Notepad. For some reason, even if I removed the extension from the filename and selected &amp;quot;All files&amp;quot;, it still saved as a &lt;code&gt;.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The trick turned out to be putting the file name in quotes. So for example, rather than saving the file as &lt;code&gt;filename&lt;/code&gt; (which results in a file named &lt;code&gt;filename.txt&lt;/code&gt;) I needed to save it as &lt;code&gt;&amp;quot;filename&amp;quot;&lt;/code&gt; (which results in &lt;code&gt;filename&lt;/code&gt; — removing the quotes but not adding the extension).&lt;/p&gt;
</content:encoded><category>windows</category></item><item><title>Add a global .gitignore</title><link>https://til.jakelazaroff.com/git/add-a-global-gitignore/</link><guid isPermaLink="true">https://til.jakelazaroff.com/git/add-a-global-gitignore/</guid><pubDate>Fri, 25 Apr 2025 10:17:17 GMT</pubDate><content:encoded>&lt;p&gt;If you use git on a Mac, chances are you&amp;#39;ve accidentally committed a &lt;code&gt;.DS_Store&lt;/code&gt; to a repo. I used to reflexively add &lt;code&gt;.DS_Store&lt;/code&gt; to all my &lt;code&gt;.gitignore&lt;/code&gt; files to avoid ever repeating that mistake.&lt;/p&gt;
&lt;p&gt;But there&amp;#39;s a better way! You can add a global &lt;code&gt;.gitignore&lt;/code&gt; file to your global config with this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git config --global core.excludesFile &amp;#39;~/.gitignore&amp;#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The single quotes around &lt;code&gt;~/.gitignore&lt;/code&gt; aren&amp;#39;t strictly necessary; if you don&amp;#39;t use them, the &lt;code&gt;~&lt;/code&gt; will just get expanded into the absolute path to your home directory.&lt;/p&gt;
&lt;p&gt;Under the hood, that command just adds this to your &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[core]
        excludesFile = ~/.gitignore
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; You can now do this without modifying your &lt;code&gt;~/.gitconfig&lt;/code&gt;! Not sure when this was added, but the &lt;a href=&quot;https://git-scm.com/docs/gitignore&quot;&gt;git documentation&lt;/a&gt; now has this bullet point:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Patterns which a user wants Git to ignore in all situations (e.g., backup or temporary files generated by the user’s editor of choice) generally go into a file specified by &lt;code&gt;core.excludesFile&lt;/code&gt; in the user&amp;#39;s &lt;code&gt;~/.gitconfig&lt;/code&gt;. Its default value is $XDG_CONFIG_HOME/git/ignore. If $XDG_CONFIG_HOME is either not set or empty, $HOME/.config/git/ignore is used instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not sure what &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt; is, but it hasn&amp;#39;t been set on any systems computers I&amp;#39;ve used. On macOS and other Unix-like systems, the default is &lt;code&gt;$HOME/.config/git/ignore&lt;/code&gt;, aka &lt;code&gt;~/.config/git/ignore&lt;/code&gt;. On Windows, that maps to &lt;code&gt;%USERPROFILE%\config\git\ignore&lt;/code&gt;.&lt;/p&gt;
</content:encoded><category>git</category></item><item><title>Parse a Date or string into a Date</title><link>https://til.jakelazaroff.com/valibot/parse-a-date-or-string-into-a-date/</link><guid isPermaLink="true">https://til.jakelazaroff.com/valibot/parse-a-date-or-string-into-a-date/</guid><pubDate>Mon, 10 Mar 2025 02:38:02 GMT</pubDate><content:encoded>&lt;p&gt;One common pattern with TypeScript projects is using a validation library like &lt;a href=&quot;https://valibot.dev/&quot;&gt;Valibot&lt;/a&gt; to validate incoming data at points of ingress to your application. (Intentionally or unintentionally, this is the &amp;quot;parse, don&amp;#39;t validate&amp;quot; pattern, coined by Alexis King in her &lt;a href=&quot;https://lexi-lambda.github.io/about.html&quot;&gt;excellent blog post of the same name&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;The pattern is a little trickier when writing code that spans network boundaries. &lt;/p&gt;
&lt;p&gt;For example, in SvelteKit, it&amp;#39;s common to load entities from a database (on a server) and then load those same entities from API endpoints (on a client). The &lt;a href=&quot;https://node-postgres.com/&quot;&gt;node-postgres&lt;/a&gt; database driver will turn timestamp columns into JavaScript &lt;code&gt;Date&lt;/code&gt; objects, but those objects will get converted to JSON before being sent over the network.&lt;/p&gt;
&lt;p&gt;So the issue is that there are two separate forms that the same entity can take. One way to solve this is by using a union validator:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { date as vdate, string, union } from &amp;quot;valibot&amp;quot;;

const date = () =&amp;gt; union([string(), vdate()]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&amp;#39;s inconvenient, though, because then the resulting type is &lt;code&gt;string | Date&lt;/code&gt;. And it&amp;#39;s not just a type system thing, either: it will &lt;em&gt;actually&lt;/em&gt; be a &lt;code&gt;string&lt;/code&gt; or &lt;code&gt;Date&lt;/code&gt;, and doing anything with it will require either type assertions or branching.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;import { date as vdate, pipe, string, transform, union } from &amp;quot;valibot&amp;quot;;

const date = () =&amp;gt;
  pipe(
    union([string(), vdate()]),
    transform(input =&amp;gt; typeof input === &amp;quot;string&amp;quot; ? new Date(input) : input)
  );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Basically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure the input type is either a &lt;code&gt;string&lt;/code&gt; or a &lt;code&gt;Date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If a &lt;code&gt;string&lt;/code&gt;, convert it to a &lt;code&gt;Date&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Boom: two different &amp;quot;input&amp;quot; representations, parsed into one consistent &amp;quot;output&amp;quot; type.&lt;/p&gt;
</content:encoded><category>valibot</category></item></channel></rss>