{
	"version": "https://jsonfeed.org/version/1",
	"title": "BWLNG",
	"icon": "https://avatars.micro.blog/avatars/2022/82.jpg",
	"home_page_url": "https://bwlng.com/",
	"feed_url": "https://bwlng.com/feed.json",
	"items": [
			
			{
				"id": "http://bowling.micro.blog/2025/07/06/ive-been-making-pizzas-in.html",
				
				"content_html": "<p>I’ve been making pizzas in the home oven with a baking steel this week. 550° for 7 minutes. Results have been great. It’s nice to cook indoors instead of outside in front of an 800° flame in 92° weather.</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fimage.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fimage.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fimage.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fimage.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fimage.jpg 1280w\" width=\"600\" height=\"600\" alt=\"A freshly baked pizza with a golden, bubbly cheese topping rests on a cooling rack.\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fb7de0bd374.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fb7de0bd374.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fb7de0bd374.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fb7de0bd374.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2Fb7de0bd374.jpg 1280w\" width=\"600\" height=\"599\" alt=\"A cheese pizza is baking on a baking steel inside an oven.\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2F58bccd2f82.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2F58bccd2f82.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2F58bccd2f82.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2F58bccd2f82.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2025%2F58bccd2f82.jpg 1280w\" width=\"450\" height=\"600\" alt=\"A hand holds a slice of pizza with a crispy crust above a plate of the remaining pizza.\" class=\"\" /></p>\n",
				"date_published": "2025-07-06T20:54:54-05:00",
				"url": "https://bwlng.com/2025/07/06/ive-been-making-pizzas-in.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/07/13/places-diary-checkins.html",
				"title": "Places Diary #5: Check-ins and Instant Image Previews for Image Uploads",
				"content_html": "<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcheck-ins-on-place2x.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcheck-ins-on-place2x.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcheck-ins-on-place2x.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcheck-ins-on-place2x.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcheck-ins-on-place2x.jpg 1280w\" width=\"700\" height=\"700\" alt=\"UI screenshot showing list of checkin-ins on a place\" class=\"\" />\n<p>One of the key features of <a href=\"https://bwlng.com/places/\">Places</a> is the ability to keep track of places you’ve been. I like to remember what I’ve tried and how it was, so Places includes “Check-ins” to add notes or a photo anytime there is something I want to remember about a place. If a note and photo is omitted, the date and time of a visit is saved. A timeline of check-ins is visible on each place’s page and on the dashboard.</p>\n<p>I’ve had the feature working for quite a while, but I wasn’t happy with the UI for adding a photo. Last weekend I finished rewriting the photo uploader so that an image preview appears instantly when an image is added to a check-in. Before, it would wait for the upload to complete before showing a preview, leaving me to wonder whether the upload was working at all when out and about or on a slow connection.</p>\n<p><video data-video playsinline muted loop preload=\"metadata\" width=\"700\" height=\"700\" class=\"\" poster=\"https://bwlng.com/uploads/2024/133b61da2f.png\"><source src=\"https://cdn.uploads.micro.blog/50/2024/check-in-on-place.mp4\" /></video></p>\n<p>Under the hood, this uses the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/FileReader\"><code>FileReader</code> API</a> which lets web apps read the contents of a file as soon as it’s selected by the user instead of waiting for it to upload and getting information about the file from the server. Using the <code>readAsDataURL()</code> method, the image is converted to a base64 string that can be displayed as in image in the browser while the image uploads.</p>\n<p>The actual image upload only begins once the image preview is displayed (which essentially happens instantly) and a loading icon is displayed over the image preview until the upload is complete.</p>\n<p>This is what the code looks:</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-tsx\" data-lang=\"tsx\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">const</span> [<span style=\"color:#a6e22e\">media</span>, <span style=\"color:#a6e22e\">setMedia</span>] <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">React</span>.<span style=\"color:#a6e22e\">useState</span>([]);\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">handleFileInputChange</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">async</span> (<span style=\"color:#a6e22e\">event</span>) <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">files</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">event</span>.<span style=\"color:#a6e22e\">target</span>.<span style=\"color:#a6e22e\">files</span>;\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">files</span>) {\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#66d9ef\">return</span>;\n</span></span><span style=\"display:flex;\"><span>  }\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#75715e\">/** Create an image preview for each file */</span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">for</span> (<span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">file</span> <span style=\"color:#66d9ef\">of</span> <span style=\"color:#a6e22e\">files</span>) {\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">reader</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">FileReader</span>();\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">reader</span>.<span style=\"color:#a6e22e\">addEventListener</span>(\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#e6db74\">&#34;load&#34;</span>,\n</span></span><span style=\"display:flex;\"><span>      () <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#75715e\">/** Convert image file to base64 string */</span>\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">img</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">Image</span>();\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">img</span>.<span style=\"color:#a6e22e\">src</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">reader</span>.<span style=\"color:#a6e22e\">result</span> <span style=\"color:#66d9ef\">as</span> <span style=\"color:#66d9ef\">string</span>;\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#75715e\">/** This is called when the read operation is completed */</span>\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">img</span>.<span style=\"color:#a6e22e\">onload</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">async</span> () <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#75715e\">/** Add the image that will be uploaded to the media state to display while the image is being uploaded */</span>\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#a6e22e\">setMedia</span>((<span style=\"color:#a6e22e\">prevMedia</span>) <span style=\"color:#f92672\">=&gt;</span> [\n</span></span><span style=\"display:flex;\"><span>            ...<span style=\"color:#a6e22e\">prevMedia</span>,\n</span></span><span style=\"display:flex;\"><span>            {\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">height</span>: <span style=\"color:#66d9ef\">img.naturalHeight</span>,\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">width</span>: <span style=\"color:#66d9ef\">img.naturalWidth</span>,\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">src</span>: <span style=\"color:#66d9ef\">img.src</span>,\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">filename</span>: <span style=\"color:#66d9ef\">file.name</span>,\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#75715e\">/** Indicates the image is being uploaded so a spinner can be shown */</span>\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">state</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;uploading&#34;</span>,\n</span></span><span style=\"display:flex;\"><span>            },\n</span></span><span style=\"display:flex;\"><span>          ]);\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#75715e\">/** Upload the image */</span>\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">formData</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">FormData</span>();\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#a6e22e\">formData</span>.<span style=\"color:#a6e22e\">append</span>(<span style=\"color:#e6db74\">&#34;file&#34;</span>, <span style=\"color:#a6e22e\">file</span>);\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>          <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">upload</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">fetch</span>(<span style=\"color:#e6db74\">&#34;/media&#34;</span>, {\n</span></span><span style=\"display:flex;\"><span>            <span style=\"color:#a6e22e\">method</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;POST&#34;</span>,\n</span></span><span style=\"display:flex;\"><span>            <span style=\"color:#a6e22e\">body</span>: <span style=\"color:#66d9ef\">formData</span>,\n</span></span><span style=\"display:flex;\"><span>          })\n</span></span><span style=\"display:flex;\"><span>            .<span style=\"color:#a6e22e\">then</span>((<span style=\"color:#a6e22e\">response</span>) <span style=\"color:#f92672\">=&gt;</span> <span style=\"color:#a6e22e\">response</span>.<span style=\"color:#a6e22e\">json</span>())\n</span></span><span style=\"display:flex;\"><span>            .<span style=\"color:#a6e22e\">then</span>((<span style=\"color:#a6e22e\">data</span>) <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#a6e22e\">setMedia</span>((<span style=\"color:#a6e22e\">prevMedia</span>) <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>                <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">prevMedia</span>.<span style=\"color:#a6e22e\">map</span>((<span style=\"color:#a6e22e\">media</span>) <span style=\"color:#f92672\">=&gt;</span> {\n</span></span><span style=\"display:flex;\"><span>                  <span style=\"color:#75715e\">/** Replace the optimistic media with the uploaded media */</span>\n</span></span><span style=\"display:flex;\"><span>                  <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">filename</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#a6e22e\">data</span>.<span style=\"color:#a6e22e\">originalFilename</span>) {\n</span></span><span style=\"display:flex;\"><span>                    <span style=\"color:#66d9ef\">return</span> {\n</span></span><span style=\"display:flex;\"><span>                      <span style=\"color:#a6e22e\">height</span>: <span style=\"color:#66d9ef\">data.height</span>,\n</span></span><span style=\"display:flex;\"><span>                      <span style=\"color:#a6e22e\">width</span>: <span style=\"color:#66d9ef\">data.width</span>,\n</span></span><span style=\"display:flex;\"><span>                      <span style=\"color:#a6e22e\">src</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">`</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">ASSETS_URL</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">/</span><span style=\"color:#e6db74\">${</span><span style=\"color:#a6e22e\">data</span>.<span style=\"color:#a6e22e\">filename</span><span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">?h=126&amp;dpr=2`</span>,\n</span></span><span style=\"display:flex;\"><span>                      <span style=\"color:#a6e22e\">filename</span>: <span style=\"color:#66d9ef\">data.originalFilename</span>,\n</span></span><span style=\"display:flex;\"><span>                    };\n</span></span><span style=\"display:flex;\"><span>                  }\n</span></span><span style=\"display:flex;\"><span>                  <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">media</span>;\n</span></span><span style=\"display:flex;\"><span>                });\n</span></span><span style=\"display:flex;\"><span>              });\n</span></span><span style=\"display:flex;\"><span>              <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">data</span>;\n</span></span><span style=\"display:flex;\"><span>            });\n</span></span><span style=\"display:flex;\"><span>        };\n</span></span><span style=\"display:flex;\"><span>      },\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#66d9ef\">false</span>\n</span></span><span style=\"display:flex;\"><span>    );\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">reader</span>.<span style=\"color:#a6e22e\">readAsDataURL</span>(<span style=\"color:#a6e22e\">file</span>);\n</span></span><span style=\"display:flex;\"><span>  }\n</span></span><span style=\"display:flex;\"><span>};\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#75715e\">/** Show the images */</span>\n</span></span><span style=\"display:flex;\"><span>{<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">map</span>((<span style=\"color:#a6e22e\">media</span>, <span style=\"color:#a6e22e\">index</span>) <span style=\"color:#f92672\">=&gt;</span> (\n</span></span><span style=\"display:flex;\"><span>  &lt;<span style=\"color:#f92672\">div</span> <span style=\"color:#a6e22e\">className</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#e6db74\">`relative h-32`</span>} <span style=\"color:#a6e22e\">key</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#a6e22e\">index</span>}&gt;\n</span></span><span style=\"display:flex;\"><span>    &lt;<span style=\"color:#f92672\">img</span>\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">height</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">height</span>}\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">width</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">width</span>}\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">src</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">src</span>}\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">className</span><span style=\"color:#f92672\">=</span>{<span style=\"color:#e6db74\">`h-full max-h-full w-auto </span><span style=\"color:#e6db74\">${</span>\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">state</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#e6db74\">&#34;uploading&#34;</span> <span style=\"color:#f92672\">?</span> <span style=\"color:#e6db74\">`opacity-50`</span> <span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">``</span>\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#e6db74\">}</span><span style=\"color:#e6db74\">`</span>}\n</span></span><span style=\"display:flex;\"><span>    /&gt;\n</span></span><span style=\"display:flex;\"><span>    {<span style=\"color:#75715e\">/** Show a spinner for media that is still uploading  */</span>}\n</span></span><span style=\"display:flex;\"><span>    {<span style=\"color:#a6e22e\">media</span>.<span style=\"color:#a6e22e\">state</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#e6db74\">&#34;uploading&#34;</span> <span style=\"color:#f92672\">?</span> &lt;<span style=\"color:#f92672\">Spinner</span> /&gt; <span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">null</span>}\n</span></span><span style=\"display:flex;\"><span>  &lt;/<span style=\"color:#f92672\">div</span>&gt;\n</span></span><span style=\"display:flex;\"><span>))}\n</span></span></code></pre></div><h2 id=\"interested-in-trying-places\">Interested in trying Places?</h2>\n<p>If you are interested in trying Places, <a href=\"https://tryplaces.com\">sign up for the waitlist</a> and be among the first to know when it’s ready.</p>\n<h2 id=\"other-posts-in-this-series\">Other posts in this series</h2>\n<ol>\n<li><a href=\"https://bwlng.com/2024/01/27/210102.html\">Places Diary #1: An Introduction</a></li>\n<li><a href=\"https://bwlng.com/2024/02/01/places-diary-an.html\">Places Diary #2: Removing Prisma</a></li>\n<li><a href=\"https://bwlng.com/2024/02/25/places-diary-side.html\">Places Diary #3: Side Quests</a></li>\n<li><a href=\"https://bwlng.com/2024/04/12/125857.html\">Places Diary #4: MapKit Tokens</a></li>\n</ol>\n",
				"date_published": "2024-07-13T14:34:01-05:00",
				"url": "https://bwlng.com/2024/07/13/places-diary-checkins.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/05/05/added-an-option.html",
				
				"content_html": "<p><video data-video playsinline muted loop preload=\"metadata\" width=\"700\" height=\"700\" class=\"\" poster=\"https://bwlng.com/uploads/2024/670c85c5d6.png\"><source src=\"https://cdn.uploads.micro.blog/50/2024/sort-places-by-date-added.mp4\" /></video></p>\n<p>Added an option in <a href=\"https://bwlng.com/places/\">Places</a> to sort by date places were saved. This is useful when you can’t remember the name of a place you saved recently or want to try somewhere new to you. Sorting still defaults to places nearest you.</p>\n",
				"date_published": "2024-05-05T10:51:30-05:00",
				"url": "https://bwlng.com/2024/05/05/added-an-option.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/26/lucky-enough-to.html",
				
				"content_html": "<p>Lucky enough to see The Killers put on an amazing show at Tipitina’s. Don’t think there was room for another person on the floor.</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fimg-0498.jpeg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fimg-0498.jpeg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fimg-0498.jpeg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fimg-0498.jpeg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fimg-0498.jpeg 1280w\" width=\"450\" height=\"600\" alt=\"\" class=\"\" />\n",
				"date_published": "2024-04-26T00:49:49-05:00",
				"url": "https://bwlng.com/2024/04/26/lucky-enough-to.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/23/created-a-page.html",
				
				"content_html": "<p>Created a page to collect all my posts on Places and describe what it is: <a href=\"https://bwlng.com/places/\">bwlng.com/places/</a></p>\n",
				"date_published": "2024-04-23T00:27:05-05:00",
				"url": "https://bwlng.com/2024/04/23/created-a-page.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/21/added-sections-to.html",
				
				"content_html": "<p><video data-video playsinline muted loop preload=\"metadata\" width=\"700\" height=\"700\" class=\"\" poster=\"https://bwlng.com/uploads/2024/c53bbf4cdc.png\"><source src=\"https://cdn.uploads.micro.blog/50/2024/places-sections-clip.mp4\" /></video></p>\n<p>Added sections to collections in <a href=\"https://bwlng.com/2024/01/27/210102.html\">Places</a>. I find them handy for organizing places in a collection by days when planning a trip, but they can be used for anything — like grouping places by category (e.g. food spots, entertainment, sights, etc.). Places can be moved into a section using drag and drop and reordered the same way. Still need to implement optimistic UI to avoid the visual jump that occurs when an item is dropped and the update is handled over the network.</p>\n",
				"date_published": "2024-04-21T15:20:25-05:00",
				"url": "https://bwlng.com/2024/04/21/added-sections-to.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/12/125857.html",
				"title": "Places Diary #4: MapKit Tokens",
				"content_html": "<p><a href=\"https://bwlng.com/2024/01/27/210102.html\">Places</a> uses Apple’s <a href=\"https://developer.apple.com/maps/web/\">MapKit JS</a> for maps and points of interest. MapKit JS requires a signed <a href=\"https://jwt.io/introduction\">JSON Web Token (JWT)</a> to authenticate against its API. I initially created an endpoint that I could load in the browser to retrieve a token that was good for a year. This made testing and development easy: save the token to a <code>.env</code> file on the server, forget about it for a year until it expires, then repeat.</p>\n<p>Before opening Places up for more people to use, I wanted to:</p>\n<ul>\n<li>Shorten the time the token was good for</li>\n<li>Remove the need to manually update it</li>\n<li>And constrain the token to specific domains prevent to others from using the token</li>\n</ul>\n<h1 id=\"providing-a-short-lived-token-to-mapkit-js\">Providing a short-lived token to MapKit JS</h1>\n<p>The <a href=\"https://developer.apple.com/documentation/mapkitjs/creating_and_using_tokens_with_mapkit_js\">MapKit JS docs</a> outline the header fields and payload needed to generate a token and the <a href=\"https://github.com/auth0/node-jsonwebtoken#readme\"><code>jsonwebtoken</code></a> package handles generating it. Set the expiration time to the number of seconds since UNIX Epoch when you want the token to expire. <code>Date.now() / 1000 + (60 * 5)</code> signs a token with a 5 minute expiration.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-js:mapkit.server.js\" data-lang=\"js:mapkit.server.js\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">import</span> <span style=\"color:#a6e22e\">jwt</span> <span style=\"color:#a6e22e\">from</span> <span style=\"color:#e6db74\">&#34;jsonwebtoken&#34;</span>;\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">export</span> <span style=\"color:#66d9ef\">async</span> <span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">getAuthorizationToken</span>({ <span style=\"color:#a6e22e\">expiration</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#ae81ff\">60</span> <span style=\"color:#f92672\">*</span> <span style=\"color:#ae81ff\">5</span>, <span style=\"color:#a6e22e\">origin</span> }) {\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">return</span> <span style=\"color:#a6e22e\">jwt</span>.<span style=\"color:#a6e22e\">sign</span>(\n</span></span><span style=\"display:flex;\"><span>    {\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">iss</span><span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">APPLE_DEVELOPER_TEAM_ID</span> <span style=\"color:#75715e\">/* Issuer: This is a 10-character Team ID obtained from your Apple Developer account. */</span>,\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">iat</span><span style=\"color:#f92672\">:</span> Date.<span style=\"color:#a6e22e\">now</span>() <span style=\"color:#f92672\">/</span> <span style=\"color:#ae81ff\">1000</span> <span style=\"color:#75715e\">/* Issued at: Number of seconds since UNIX Epoch, in UTC */</span>,\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">exp</span><span style=\"color:#f92672\">:</span> Date.<span style=\"color:#a6e22e\">now</span>() <span style=\"color:#f92672\">/</span> <span style=\"color:#ae81ff\">1000</span> <span style=\"color:#f92672\">+</span> <span style=\"color:#a6e22e\">expiration</span> <span style=\"color:#75715e\">/* Expiration: When the token expires, in terms of the number of seconds since UNIX Epoch, in UTC */</span>,\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">origin</span><span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">origin</span> <span style=\"color:#75715e\">/* An optional claim that constrains the token to a specific website or domain. The value of this claim is a fully qualified domain, without a URL scheme */</span>,\n</span></span><span style=\"display:flex;\"><span>    },\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">APPLE_MAPS_AUTH_KEY</span> <span style=\"color:#75715e\">/* The private key that you obtain from your Apple Developer account */</span>,\n</span></span><span style=\"display:flex;\"><span>    {\n</span></span><span style=\"display:flex;\"><span>      <span style=\"color:#a6e22e\">header</span><span style=\"color:#f92672\">:</span> {\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">kid</span><span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">APPLE_MAPS_KEY_ID</span> <span style=\"color:#75715e\">/* A 10-character key identifier that provides the ID of the private key that you obtain from your Apple Developer account. */</span>,\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">typ</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;JWT&#34;</span> <span style=\"color:#75715e\">/* The type, “JWT” */</span>,\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">alg</span><span style=\"color:#f92672\">:</span> <span style=\"color:#e6db74\">&#34;ES256&#34;</span> <span style=\"color:#75715e\">/* The algorithm you use to encrypt the token. Use the ES256 algorithm */</span>,\n</span></span><span style=\"display:flex;\"><span>      }\n</span></span><span style=\"display:flex;\"><span>    }\n</span></span><span style=\"display:flex;\"><span>  );\n</span></span><span style=\"display:flex;\"><span>}\n</span></span></code></pre></div><p>A endpoint is needed so the token can be retrieved. The route verifies the request is from an allowed origin, calls the <code>getAuthorizationToken</code> function constrained to the server’s hostname with a 5 minute expiration, and then returns it.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-js:routes/mapkit-token.jsx\" data-lang=\"js:routes/mapkit-token.jsx\"><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">import</span> { <span style=\"color:#a6e22e\">getAuthorizationToken</span> } <span style=\"color:#a6e22e\">from</span> <span style=\"color:#e6db74\">&#34;~/mapkit.server&#34;</span>;\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span><span style=\"color:#66d9ef\">export</span> <span style=\"color:#66d9ef\">async</span> <span style=\"color:#66d9ef\">function</span> <span style=\"color:#a6e22e\">loader</span>({ <span style=\"color:#a6e22e\">request</span> }) {\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">url</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">URL</span>(<span style=\"color:#a6e22e\">request</span>.<span style=\"color:#a6e22e\">url</span>);\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">serverHostName</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">SERVER_HOSTNAME</span>;\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">allowedOrigins</span> <span style=\"color:#f92672\">=</span> [<span style=\"color:#e6db74\">&#34;localhost:3000&#34;</span>, <span style=\"color:#e6db74\">&#34;example.com&#34;</span>];\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#75715e\">/** Get the fully qualified domain, without a URL scheme, from the request */</span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">origin</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#a6e22e\">url</span>.<span style=\"color:#a6e22e\">origin</span>.<span style=\"color:#a6e22e\">replace</span>(<span style=\"color:#e6db74\">/^https?:\\/\\//</span>, <span style=\"color:#e6db74\">&#34;&#34;</span>);\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#75715e\">/** Check if the origin is allowed */</span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">if</span> (<span style=\"color:#f92672\">!</span><span style=\"color:#a6e22e\">allowedOrigins</span>.<span style=\"color:#a6e22e\">includes</span>(<span style=\"color:#a6e22e\">origin</span>) <span style=\"color:#f92672\">||</span> <span style=\"color:#a6e22e\">origin</span> <span style=\"color:#f92672\">!==</span> <span style=\"color:#a6e22e\">serverHostName</span>) {\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">Response</span>(<span style=\"color:#e6db74\">&#34;Request not allowed&#34;</span>, { <span style=\"color:#a6e22e\">status</span><span style=\"color:#f92672\">:</span> <span style=\"color:#ae81ff\">403</span> });\n</span></span><span style=\"display:flex;\"><span>  }\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#75715e\">/** Generate a new authorization token */</span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">const</span> <span style=\"color:#a6e22e\">authorizationToken</span> <span style=\"color:#f92672\">=</span> <span style=\"color:#66d9ef\">await</span> <span style=\"color:#a6e22e\">getAuthorizationToken</span>({\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">expiration</span><span style=\"color:#f92672\">:</span> <span style=\"color:#ae81ff\">60</span> <span style=\"color:#f92672\">*</span> <span style=\"color:#ae81ff\">5</span> <span style=\"color:#75715e\">/** five minutes */</span>,\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">origin</span><span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">NODE_ENV</span> <span style=\"color:#f92672\">===</span> <span style=\"color:#e6db74\">&#34;development&#34;</span> <span style=\"color:#f92672\">?</span> <span style=\"color:#66d9ef\">undefined</span> <span style=\"color:#f92672\">:</span> <span style=\"color:#a6e22e\">process</span>.<span style=\"color:#a6e22e\">env</span>.<span style=\"color:#a6e22e\">SERVER_HOSTNAME</span> <span style=\"color:#75715e\">/** `localhost` isn’t a valid origin, so omit this in development */</span>,\n</span></span><span style=\"display:flex;\"><span>  });\n</span></span><span style=\"display:flex;\"><span>\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#66d9ef\">return</span> <span style=\"color:#66d9ef\">new</span> <span style=\"color:#a6e22e\">Response</span>(<span style=\"color:#a6e22e\">authorizationToken</span>);\n</span></span><span style=\"display:flex;\"><span>}\n</span></span></code></pre></div><p>Now, instead of manually adding a token that needs to be updated when it expires, the <code>authorizationCallback</code> function MapKit JS requires for initialization can fetch a token from the endpoint. If MapKit JS detects an expired token, it will fetch a new one by calling the <code>authorizationCallback</code> function again.</p>\n<div class=\"highlight\"><pre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-js\" data-lang=\"js\"><span style=\"display:flex;\"><span><span style=\"color:#a6e22e\">mapkit</span>.<span style=\"color:#a6e22e\">init</span>({\n</span></span><span style=\"display:flex;\"><span>  <span style=\"color:#a6e22e\">authorizationCallback</span><span style=\"color:#f92672\">:</span> <span style=\"color:#66d9ef\">function</span>(<span style=\"color:#a6e22e\">done</span>) {\n</span></span><span style=\"display:flex;\"><span>    <span style=\"color:#a6e22e\">fetch</span>(<span style=\"color:#e6db74\">&#34;/mapkit-token&#34;</span>)\n</span></span><span style=\"display:flex;\"><span>      .<span style=\"color:#a6e22e\">then</span>(<span style=\"color:#a6e22e\">res</span> =&gt; <span style=\"color:#a6e22e\">res</span>.<span style=\"color:#a6e22e\">text</span>())\n</span></span><span style=\"display:flex;\"><span>      .<span style=\"color:#a6e22e\">then</span>(<span style=\"color:#a6e22e\">done</span>)\n</span></span><span style=\"display:flex;\"><span>      .<span style=\"color:#66d9ef\">catch</span>(<span style=\"color:#a6e22e\">error</span> =&gt; {\n</span></span><span style=\"display:flex;\"><span>        <span style=\"color:#a6e22e\">console</span>.<span style=\"color:#a6e22e\">error</span>(<span style=\"color:#a6e22e\">error</span>.<span style=\"color:#a6e22e\">message</span>, <span style=\"color:#e6db74\">&#34;Error fetching Apple Maps token&#34;</span>);\n</span></span><span style=\"display:flex;\"><span>      });\n</span></span><span style=\"display:flex;\"><span>  },\n</span></span><span style=\"display:flex;\"><span>});\n</span></span></code></pre></div><h2 id=\"interested-in-trying-places\">Interested in trying Places?</h2>\n<p>If you are interested in trying Places, <a href=\"https://tryplaces.com\">sign up for the waitlist</a> and be among the first to know when it’s ready.</p>\n<h2 id=\"other-posts-in-this-series\">Other posts in this series</h2>\n<ol>\n<li><a href=\"https://bwlng.com/2024/01/27/210102.html\">Places Diary #1: An Introduction</a></li>\n<li><a href=\"https://bwlng.com/2024/02/01/places-diary-an.html\">Places Diary #2: Removing Prisma</a></li>\n<li><a href=\"https://bwlng.com/2024/02/25/places-diary-side.html\">Places Diary #3: Side Quests</a></li>\n</ol>\n",
				"date_published": "2024-04-12T12:58:57-05:00",
				"url": "https://bwlng.com/2024/04/12/125857.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/09/224439.html",
				"title": "Pizzaversary: New York",
				"content_html": "<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fpizzaversary-ny-collage.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fpizzaversary-ny-collage.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fpizzaversary-ny-collage.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fpizzaversary-ny-collage.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fpizzaversary-ny-collage.jpg 1280w\" width=\"600\" height=\"360\" alt=\"Collage of 15 pizzas from 14 pizza places in New York\" class=\"\" />\n<p>Erin and I spent the weekend in New York celebrating our 11th wedding anniversary with 14 pizza places, 6 comedy shows, Sweeney Todd on Broadway, and an earthquake. We set out with an ambitious list of 22 pizza places for consideration. We knew that wasn’t likely in five days (which included two travel days). We ended up trying 14.</p>\n<h2 id=\"rating-places\">Rating Places</h2>\n<p>We created a rubric this year and rated each place based on crust, sauce, cheese, and toppings. Each criterion was rated on a scale of 1 to 5 (bad, meh, good, great, exceptional; no half points). Points were totaled, then divided by four for an overall score.</p>\n<h2 id=\"the-places\">The Places</h2>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F09-una.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F09-una.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F09-una.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F09-una.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F09-una.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Margherita pizza with billowy crust\" class=\"\" />\n<p><strong>Una (Overall: 4.75)</strong> - Crust: 5, Sauce: 4, Cheese: 5, Toppings: 5</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F08-rubirosa.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F08-rubirosa.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F08-rubirosa.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F08-rubirosa.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F08-rubirosa.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Pizza with vodka sauce, tomato sauce, fresh mozzarella, and a spiral of pesto sauce\" class=\"\" />\n<p><strong>Rubirosa (Overall: 4.75)</strong> - Crust: 4, Sauce: 5, Cheese: 5, Toppings: 5</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F13-ops.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F13-ops.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F13-ops.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F13-ops.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F13-ops.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Margherita pizza with fresh basil\" class=\"\" />\n<p><strong>Ops (Overall: 4.25)</strong> - Crust: 4, Sauce: 5, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F03-ribalta.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F03-ribalta.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F03-ribalta.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F03-ribalta.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F03-ribalta.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Margherita pizza with basil\" class=\"\" />\n<p><strong>Ribalta (Overall: 4.25)</strong> - Crust: 4, Sauce: 5, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F05-razza.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F05-razza.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F05-razza.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F05-razza.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F05-razza.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Margherita pizza with charred basil and dark crust\" class=\"\" />\n<p><strong>Razza (Overall: 4)</strong> - Crust: 4, Sauce: 4, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F01-l-industrie.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F01-l-industrie.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F01-l-industrie.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F01-l-industrie.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F01-l-industrie.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Slice of pepperoni pizza topped with fresh basil and shredded cheese\" class=\"\" />\n<p><strong>L’Industrie (Overall: 4)</strong> - Crust: 4, Sauce: 4, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F12-pizza-secret.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F12-pizza-secret.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F12-pizza-secret.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F12-pizza-secret.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F12-pizza-secret.jpg 1280w\" width=\"600\" height=\"600\" alt=\"White four cheese pizza with charred crust\" class=\"\" />\n<p><strong>Pizza Secret (Overall: 4)</strong> - Crust: 4, Sauce: 4, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F11-robertas.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F11-robertas.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F11-robertas.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F11-robertas.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F11-robertas.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Bee Sting pizza with soppressata, mozzarella, and tomato sauce\" class=\"\" />\n<p><strong>Roberta’s (Overall: 3.75)</strong> - Crust: 4, Sauce: 4, Cheese: 3, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F07-keste.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F07-keste.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F07-keste.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F07-keste.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F07-keste.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Pizza with mozzarella and soppressata\" class=\"\" />\n<p><strong>Kesté (Overall: 4)</strong> - Crust: 4, Sauce: 3, Cheese: 5, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F04-song-e-napule.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F04-song-e-napule.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F04-song-e-napule.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F04-song-e-napule.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F04-song-e-napule.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Pizza with mozzarella and strips of soppressata\" class=\"\" />\n<p><strong>Song E Napule (Overall: 3.75)</strong> - Crust: 3, Sauce: 4, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F10-juliana.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F10-juliana.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F10-juliana.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F10-juliana.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F10-juliana.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Thin crust pizza with fresh mozzarella and charred basil\" class=\"\" />\n<p><strong>Juliana’s (Overall: 3.5)</strong> - Crust: 3, Sauce: 3, Cheese: 4, Toppings: 4</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F02-best-pizza.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F02-best-pizza.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F02-best-pizza.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F02-best-pizza.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F02-best-pizza.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Slice of cheese pizza on a paper plate on a counter\" class=\"\" />\n<p><strong>Best Pizza (Overall: 3)</strong> - Crust: 3, Sauce: 3, Cheese: 3, Toppings: 3</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F06-joes.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F06-joes.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F06-joes.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F06-joes.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F06-joes.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Slice of cheese pizza held in hand on a paper plate\" class=\"\" />\n<p><strong>Joe’s (Overall: 3)</strong> - Crust: 3, Sauce: 3, Cheese: 3, Toppings: 3</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F14-prince-st.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F14-prince-st.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F14-prince-st.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F14-prince-st.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2F14-prince-st.jpg 1280w\" width=\"600\" height=\"600\" alt=\"Slice of Margherita pizza in a box with a square of cupped pepperoni pizza and a square of cheese pizza\" class=\"\" />\n<p><strong>Prince (Overall: 3)</strong> - Crust: 2, Sauce: 3, Cheese: 3, Toppings: 4</p>\n<h2 id=\"the-results\">The Results</h2>\n<p>In the end, I had 33.75 pieces of pizza and enjoyed 31.75 of them. If I were to visit New York and could only choose one place to eat, Una would be my pick. The crust and the craft they put into their pies are on another level. Among the New York-style pizzas we tried, the Tie-Dye pizza at Rubirosa was my favorite. I highly recommend trying any of the places with an overall rating above 3.5 if you find yourself nearby. I wouldn’t be disappointed if any of those places were my hometown pizza spot.</p>\n<h2 id=\"the-rest\">The Rest</h2>\n<p>The places under consideration we didn&rsquo;t make it to were Lucali, Scarr’s, Don Antonio, Motorino, Totonno’s, Pasquale Jones, Salsa, and San Matteo. We went to John&rsquo;s of Bleecker St. last time we were in the city, so we didn&rsquo;t include it for consideration this time. We’ll have to make another trip.</p>\n",
				"date_published": "2024-04-10T01:13:32-05:00",
				"url": "https://bwlng.com/2024/04/09/224439.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/09/github-and-other.html",
				
				"content_html": "<p>GitHub and other websites started rendering text with similar but different (weird) characters in my browser.</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-ns2x.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-ns2x.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-ns2x.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-ns2x.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-ns2x.jpg 1280w\" width=\"442\" height=\"357\" alt=\"Screenshot of code on a webpage where words like `export` are rendered as `èxpört`\" class=\"\" />\n<p>Tracked it down to monospaced fonts rendering with <code>.SF NS</code> instead of <code>SF Mono</code>. No idea what happened, but deleting SF Mono from <code>/Users/username/Library/Fonts</code> and reinstalling it fixed it.</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-mono2x.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-mono2x.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-mono2x.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-mono2x.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fcode-sf-mono2x.jpg 1280w\" width=\"442\" height=\"357\" alt=\"Screenshot of a code snippet on a webpage rendered as expected\" class=\"\" />\n",
				"date_published": "2024-04-09T11:29:01-05:00",
				"url": "https://bwlng.com/2024/04/09/github-and-other.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/04/05/just-felt-this.html",
				
				"content_html": "<p>Just felt <a href=\"https://www.nytimes.com/2024/04/05/nyregion/earthquake-new-york-boston-philadelphia.html\">this 4.7 magnitude earthquake in NYC.</a> Crews had been jackhammering all morning, so I thought it was construction related until I saw the reports 😅</p>\n",
				"date_published": "2024-04-05T09:48:53-05:00",
				"url": "https://bwlng.com/2024/04/05/just-felt-this.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/02/25/im-now-bwlngcom.html",
				
				"content_html": "<p>I’m now <a href=\"https://bsky.app/profile/bwlng.com\">@bwlng.com</a> on Bluesky thanks to Micro.blog adding <a href=\"https://www.manton.org/2024/02/08/bluesky-domain-handles.html\">AT Protocol DIDs for hosted blogs.</a></p>\n<p>And, no, I had no clue what a &ldquo;DID&rdquo; was until I read that and Googled <a href=\"https://atproto.com/specs/did\">this.</a></p>\n",
				"date_published": "2024-02-25T22:13:43-05:00",
				"url": "https://bwlng.com/2024/02/25/im-now-bwlngcom.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2024/02/25/places-diary-side.html",
				"title": "Places Diary #3: Side Quests",
				"content_html": "<p><a href=\"https://bwlng.com/2024/02/01/places-diary-an.html\">After parting ways with Prisma,</a> I dedicated time to shoring up a few other aspects of <a href=\"https://bwlng.com/2024/01/27/210102.html\">the project.</a></p>\n<h2 id=\"type-safe-sql-queries\">Type-safe SQL queries</h2>\n<p>Initially opting for raw SQL queries post-Prisma, I’ve now refactored once more to use <a href=\"https://kysely.dev\">Kysely</a>, a lightweight SQL query builder. It still uses <a href=\"https://github.com/WiseLibs/better-sqlite3\"><code>better-sqlite3</code></a> under the hood, but adds type-safe SQL queries and exposes the database schema to the TypeScript compiler for autocompletion based on the database schema. This helps prevent common errors like typos and referencing non-existant columns.</p>\n<h2 id=\"migrations\">Migrations</h2>\n<p>Replacing Prisma also meant needing to find a new way to handle migrations. Thankfully, Kysely <a href=\"https://kysely.dev/docs/migrations#sqlite-migration-example\">provides examples for handling migrations.</a> That paired with <a href=\"https://gist.github.com/bwlng/6d7a87916236bc14cef0bda93267d621\">a script</a> to execute them on each deploy addressed the remaining gaps from removing Prisma.</p>\n<h2 id=\"remix-v1-to-remix-v2-plus-esm-and-vite\">Remix v1 to Remix v2 (plus ESM and Vite)</h2>\n<p>I also updated from Remix v1 to v2. Remix’s <a href=\"https://remix.run/docs/en/main/start/future-flags\">“Future Flags”</a> made it pretty straightforward to adopt v2 changes while still on v1 and then seamlessly make the upgrade to v2 once I had each new feature working.</p>\n<p>Remix v2 uses ESM as the default server module output, so I adopted that as well. The templates <code>create-remix</code> uses to create a new project are in the Remix repo, so it was simple enough to copy the ESM server modules from <a href=\"https://github.com/remix-run/remix/tree/main/templates/express\">the Express template</a> over the CJS modules that needed to be updated.</p>\n<p>Once on v2, I replaced Remix’s compiler with <a href=\"https://remix.run/docs/en/main/future/vite\">Vite</a>, which Remix plans to make its default in the future. Remix’s compiler requires <a href=\"https://remix.run/docs/en/main/guides/gotchas#importing-esm-packages\">a workaround</a> for bundling ESM server modules. Moving to Vite allowed me to quit tracking the dependencies that Remix needed to bundle differently since Vite supports both ESM and CJS dependencies.</p>\n<h2 id=\"one-annoying-issue\">One Annoying Issue</h2>\n<p>The move to Vite has introduced one annoying issue. Occasionally after making a change, the dev server will show <code>504 (Outdated Optimize Dep)</code> errors in Safari’s console (it doesn’t seem to affect Chromium browsers). This results in interactive features not working until cached data is cleared for <code>localhost</code> (Safari → Settings → Privacy → Manage Website Data). Everything works fine in production, so I will track <a href=\"https://github.com/remix-run/remix/issues/8803\">the issue at GitHub</a> and hope it gets resolved soon.</p>\n<p>Now back to new features…</p>\n<h2 id=\"interested-in-trying-places\">Interested in trying Places?</h2>\n<p>If you are interested in trying Places, <a href=\"https://tryplaces.com\">sign up for the waitlist</a> and be among the first to know when it’s ready.</p>\n<h2 id=\"other-posts-in-this-series\">Other Posts In This Series</h2>\n<ol>\n<li><a href=\"https://bwlng.com/2024/01/27/210102.html\">Places Diary #1: An Introduction</a></li>\n<li><a href=\"https://bwlng.com/2024/02/01/places-diary-an.html\">Places Diary #2: Removing Prisma</a></li>\n</ol>\n",
				"date_published": "2024-02-25T18:10:58-05:00",
				"url": "https://bwlng.com/2024/02/25/places-diary-side.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/02/01/places-diary-an.html",
				"title": "Places Diary #2: Removing Prisma",
				"content_html": "<p>I started building <a href=\"https://bwlng.com/2024/01/27/210102.html\">Places</a> using a Remix &ldquo;stack&rdquo; that made some decisions about development tools so I could get started iterating on ideas instead of spending time doing a bunch of tedious setup. One of the decisions that came with the stack was the use of Prisma as an ORM.</p>\n<p>Prisma makes a lot of things easy:</p>\n<ul>\n<li>Connecting to a database</li>\n<li>Creating the schema</li>\n<li>Handling migrations</li>\n<li>Querying the database</li>\n<li>CRUD operations</li>\n</ul>\n<p>But there are other things you can’t do at all.</p>\n<h2 id=\"things-that-i-had-to-work-around-when-using-prisma\">Things that I had to work around when using Prisma</h2>\n<h3 id=\"math\">Math</h3>\n<p>Prisma embeds its own binary of SQLite and it does not have SQLite’s <a href=\"https://www.sqlite.org/lang_mathfunc.html\">math functions</a> enabled. Places is a location-based app and location-based apps rely on the <a href=\"https://en.wikipedia.org/wiki/Haversine_formula\">Haversine formula</a> to calculate the geographical distance between points. It was possible to work around this by calculating the straight-line distance between points, but straight-line distance gets less accurate as points get farther apart.</p>\n<h3 id=\"polymorphic-associations\">Polymorphic associations</h3>\n<p>In Places you can add media to a location and you can add media to a check-in. It’s common to use a single table to store these types of associations in a clean and extensible manner. Prisma prefers to create a new table for each type of association. This results in a more complex schema, more complex queries, and more cumbersome maintenance over time. There has been <a href=\"https://github.com/prisma/prisma/issues/1644\">an issue tracking polymorphic associations</a> and <a href=\"https://github.com/prisma/prisma/issues/2505\">unions</a> for four years, but neither are on Prisma’s roadmap as of this writing.</p>\n<h3 id=\"querying-recursive-relationships\">Querying recursive relationships</h3>\n<p>Places are assigned categories and those categories are  organized in a tree structure (e.g. filtering by Asian restaurants will show all Chinese, Thai, and Sushi restaurants). With Prisma, it’s possible to create those relationships, but you have to know the maximum level of nested categories to query them because it doesn’t <a href=\"https://github.com/prisma/prisma/issues/3725\">support recursive queries</a>.</p>\n<h3 id=\"search\">Search</h3>\n<p>Primsa added support for <a href=\"https://github.com/prisma/prisma/releases/tag/2.30.0\">full-text search for PostgreSQL</a> in 2021. As of February 2024, they have not added full-text search support for SQLite. This means search in Places has been limited to simple pattern matching searches, such as finding saved places with names containing certain letters or words.</p>\n<h3 id=\"compiling-sqlite\">Compiling SQLite</h3>\n<p>Prisma includes its own SQLite binary and I’m not sure if it can be compiled with custom options. I’ve seen <a href=\"https://github.com/prisma/prisma/discussions/4108#discussioncomment-118931\">mention of compiling your own</a>, but I haven’t seen any examples or docs for doing that. While it’s possible to drop down to raw queries in Prisma, raw queries won’t help if you need functions that aren’t enabled.</p>\n<h2 id=\"removing-prisma\">Removing Prisma</h2>\n<p>Because of all the challenges above, I decided to remove Prisma from the project and refactor all the CRUD operations to raw SQL. I’m using <a href=\"https://github.com/WiseLibs/better-sqlite3\"><code>better-sqlite3</code></a> to interact with database. It was pretty painless to make the switch and now (for better or worse) I am in control of the database schema and queries. <code>better-sqlite3</code> also bundles its own SQLite binary, but the bundled version is compiled with support for math functions and full-text search. The docs also <a href=\"https://github.com/WiseLibs/better-sqlite3/blob/master/docs/compilation.md\">include information for compiling a custom configuration</a> should I need to in the future.</p>\n<p>Prisma was helpful for getting started, but I think replacing its abstraction with direct interaction with SQLite will be more productive and flexible in the long term.</p>\n<h2 id=\"interested-in-trying-places\">Interested in trying Places?</h2>\n<p>If you are interested in trying Places, <a href=\"https://tryplaces.com\">sign up for the waitlist</a> and be among the first to know when it’s ready.</p>\n<h2 id=\"other-posts-in-this-series\">Other Posts In This Series</h2>\n<ol>\n<li><a href=\"https://bwlng.com/2024/01/27/210102.html\">Places Diary #1: An Introduction</a></li>\n</ol>\n",
				"date_published": "2024-02-06T23:51:11-05:00",
				"url": "https://bwlng.com/2024/02/01/places-diary-an.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2024/01/27/210102.html",
				"title": "Places Diary #1: An Introduction",
				"content_html": "<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fplaces-hero-16-9-2x.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fplaces-hero-16-9-2x.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fplaces-hero-16-9-2x.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fplaces-hero-16-9-2x.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2024%2Fplaces-hero-16-9-2x.jpg 1280w\" width=\"1200\" height=\"674\" alt=\"Keep track of places you’ve been and places you want to go.\" class=\"\" />\n<p>Places is a web app I’m building to keep track of places I’ve been and places I want to go. I’ve been working on it on and off for about 18 months in my spare time and have over 300 code commits in the repo.</p>\n<h2 id=\"what-is-it\">What is it?</h2>\n<p>It’s essentially a places organizer. You can search for places (restaurants, breweries, museums, venues, or anything else that interests you), save them, and add notes — like why you are saving it or a recommendation from a friend who&rsquo;s been there.</p>\n<p>You can also create collections of places you save. A couple of my favorites are <em>50 Top Pizza Spots in the United Sates</em> and <em>Date Night</em>. I also make a collection before each trip I take of the places I want to try in whatever city I’m visiting.</p>\n<p>Some other niceties include:</p>\n<ul>\n<li>Filters: view all places of a certain category, whether or not you’ve visited before, and your favorites</li>\n<li>Check-ins: add photos and notes when you visit a place to remember what was good or what to avoid next time</li>\n</ul>\n<p>For the most part, Places is meant to be personal, but you can create a public link for any of your collections and share with anyone to view. It’s useful for sharing places with people you are traveling with or recommendations for a friend who is visiting.</p>\n<h2 id=\"the-technology\">The technology</h2>\n<p>I’m trying to keep things relatively simple by avoiding too many NPM packages and limiting third party services. The app is deployed on a traditional server running Linux and Node.js and built using:</p>\n<ul>\n<li><a href=\"https://remix.run\">Remix</a>: web framework</li>\n<li><a href=\"https://tailwindcss.com\">Tailwind</a>: css framework</li>\n<li><a href=\"https://www.sqlite.org\">SQLite</a>: database</li>\n<li><a href=\"https://www.prisma.io\">Prisma</a>: ORM (but I’m working on removing it due to it’s limitations with <a href=\"https://github.com/prisma/prisma/discussions/16815\">math</a>, <a href=\"https://github.com/prisma/prisma/issues/2505\">unions</a>/<a href=\"https://github.com/prisma/prisma/issues/1644\">polymorphism</a>, <a href=\"https://github.com/prisma/prisma/issues/9414\">search</a>, etc.)</li>\n<li><a href=\"https://developer.apple.com/maps/web/\">MapKit JS</a>: maps, points of interest</li>\n</ul>\n<p>The app has been functional enough for me to use as my main “place-saver” for over a year, but there is still plenty to add and polish.</p>\n<h2 id=\"interested-in-trying-places\">Interested in trying Places?</h2>\n<p>If you are interested in trying Places, <a href=\"https://tryplaces.com\">sign up for the waitlist</a> and be among the first to know when it’s ready.</p>\n",
				"date_published": "2024-01-27T23:33:27-05:00",
				"url": "https://bwlng.com/2024/01/27/210102.html",
				"tags": ["Places"]
			},
			
			{
				"id": "http://bowling.micro.blog/2023/08/25/good-day-for.html",
				
				"content_html": "<p>Good day for new music. <a href=\"https://songwhip.com/zach-bryan/zach-bryan\">Zach Bryan</a>, <a href=\"https://songwhip.com/turnpike-troubadours/a-cat-in-the-rain\">Turnpike Troubadours</a>, and <a href=\"https://songwhip.com/morganwade/psychopath2023\">Morgan Wade</a>.</p>\n<p>And Zach acknowledges Turnpike on a track:</p>\n<blockquote>\n<p>“I heard Turnpike’s back together and they’re writing songs”</p>\n</blockquote>\n",
				"date_published": "2023-08-25T08:02:52-05:00",
				"url": "https://bwlng.com/2023/08/25/good-day-for.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2023/08/23/looks-like-dr.html",
				
				"content_html": "<p>Looks like <a href=\"https://www.instagram.com/reel/CwNaVefBcMq/?igshid=MzRlODBiNWFlZA==\">Dr. Dog is back in the studio.</a> Queuing up <a href=\"https://songwhip.com/ddtobyleaman/military-applications\">Toby’s</a> and <a href=\"https://songwhip.com/scottmcmickenandtheeverexpanding/shabang\">Scott’s</a> recent solo albums for the occasion.</p>\n",
				"date_published": "2023-08-23T08:35:37-05:00",
				"url": "https://bwlng.com/2023/08/23/looks-like-dr.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2023/08/04/225110.html",
				
				"content_html": "<p>Pizzas with a quick 18 hour dough. Two cheese with red sauce, an olive oil and garlic based pizza with seven cheeses (mozzarella and provolone blend, four cheese blend, and burrata), and peppadews, mushrooms, and mozzarella with red sauce.</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fekn3m.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fekn3m.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fekn3m.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fekn3m.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fekn3m.jpg 1280w\" width=\"600\" height=\"799\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fyhvud.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fyhvud.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fyhvud.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fyhvud.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fyhvud.jpg 1280w\" width=\"600\" height=\"800\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fknfyy.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fknfyy.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fknfyy.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fknfyy.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Fknfyy.jpg 1280w\" width=\"600\" height=\"800\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Frpc9y.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Frpc9y.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Frpc9y.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Frpc9y.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fcdn.uploads.micro.blog%2F50%2F2023%2Frpc9y.jpg 1280w\" width=\"600\" height=\"799\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2023-08-04T22:51:10-05:00",
				"url": "https://bwlng.com/2023/08/04/225110.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2023/01/17/ive-been-waiting.html",
				
				"content_html": "<p>I’ve been waiting on updates to the Mac Mini to replace my home media server. The 8 TB storage, 32 GB memory, M2 Pro configuration is only $200 less than a similar configuration of the M1 Max Mac Studio. What to do?</p>\n",
				"date_published": "2023-01-17T10:40:17-05:00",
				"url": "https://bwlng.com/2023/01/17/ive-been-waiting.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2022/06/14/etbowlinginstagramcom-and-i.html",
				
				"content_html": "<p><a href=\"https://www.instagram.com/etbowling\">@etbowling@instagram.com</a> and I are excited to announce we’ve got something in the oven! A baby boy! For delivery this October 👶🏻🍕</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fnzvyz.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fnzvyz.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fnzvyz.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fnzvyz.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fnzvyz.jpg 1280w\" width=\"600\" height=\"600\" alt=\"\" class=\"\" />\n",
				"date_published": "2022-06-14T12:57:55-05:00",
				"url": "https://bwlng.com/2022/06/14/etbowlinginstagramcom-and-i.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/09/10/more-pizza-alfredo.html",
				
				"content_html": "<p>More pizza.</p>\n<p>Alfredo and cheese (uncharred) for the kids\nPepperoni and boudin\nPesto, black olives, onions, and feta\nGarlic, tomato, burrata\nPepperoni, olive, onion, boudin\nPepperoni, boudin, burrata</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpgeni.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpgeni.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpgeni.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpgeni.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpgeni.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F9n116.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F9n116.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F9n116.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F9n116.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F9n116.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F2qcr9.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F2qcr9.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F2qcr9.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F2qcr9.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F2qcr9.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fzivxn.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fzivxn.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fzivxn.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fzivxn.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fzivxn.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fd5aa9.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fd5aa9.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fd5aa9.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fd5aa9.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fd5aa9.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fl20b7.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fl20b7.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fl20b7.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fl20b7.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fl20b7.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fmnexh.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fmnexh.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fmnexh.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fmnexh.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fmnexh.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Feahue.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Feahue.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Feahue.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Feahue.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Feahue.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F1i4qx.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F1i4qx.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F1i4qx.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F1i4qx.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F1i4qx.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2021-09-10T21:23:20-05:00",
				"url": "https://bwlng.com/2021/09/10/more-pizza-alfredo.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/09/06/second-pies-at.html",
				
				"content_html": "<p>60 second pies at 918° in the new oven.</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fwvsc7.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fwvsc7.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fwvsc7.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fwvsc7.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fwvsc7.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fn4u58.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fn4u58.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fn4u58.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fn4u58.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fn4u58.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fcvtf8.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fcvtf8.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fcvtf8.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fcvtf8.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fcvtf8.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Famo3r.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Famo3r.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Famo3r.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Famo3r.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Famo3r.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpmcxh.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpmcxh.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpmcxh.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpmcxh.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fpmcxh.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2021-09-06T08:14:18-05:00",
				"url": "https://bwlng.com/2021/09/06/second-pies-at.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/08/07/last-nights-pizza.html",
				
				"content_html": "<p>Last night’s pizza. Mozzarella, pepperonis, kalamata olives, and feta. Extra crispy.</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ffz6jw.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ffz6jw.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ffz6jw.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ffz6jw.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ffz6jw.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fse5m2.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fse5m2.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fse5m2.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fse5m2.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fse5m2.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F11ipt.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F11ipt.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F11ipt.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F11ipt.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F11ipt.jpg 1280w\" width=\"481\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fu8gun.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fu8gun.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fu8gun.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fu8gun.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fu8gun.jpg 1280w\" width=\"480\" height=\"600\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2021-08-07T08:15:53-05:00",
				"url": "https://bwlng.com/2021/08/07/last-nights-pizza.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/05/29/last-nights-pizza.html",
				
				"content_html": "<p>Last night’s pizza with sourdough from <a href=\"https://www.instagram.com/marubreadco\">@marubreadco@instagram.com</a> and pepperoni from <a href=\"https://www.instagram.com/iverstinefarms\">@iverstinefarms@instagram.com</a> 🍕🤤</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fdkk4w.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fdkk4w.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fdkk4w.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fdkk4w.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fdkk4w.jpg 1280w\" width=\"600\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ff4c6i.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ff4c6i.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ff4c6i.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ff4c6i.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Ff4c6i.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbrooj.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbrooj.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbrooj.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbrooj.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbrooj.jpg 1280w\" width=\"600\" height=\"600\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2021-05-29T11:49:09-05:00",
				"url": "https://bwlng.com/2021/05/29/last-nights-pizza.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/04/01/this-was-a.html",
				
				"content_html": "<p>This was a delicious experiment. Made a s’mores inspired Peepza with Nutella, and Peeps. Blasted it in the pizza oven, then dusted it with graham crackers. 😋</p>\n<p><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fy8j9r.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fy8j9r.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fy8j9r.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fy8j9r.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fy8j9r.jpg 1280w\" width=\"600\" height=\"600\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fhrpji.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fhrpji.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fhrpji.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fhrpji.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fhrpji.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F7cs5d.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F7cs5d.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F7cs5d.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F7cs5d.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2F7cs5d.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fau1k7.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fau1k7.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fau1k7.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fau1k7.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fau1k7.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fxrzfr.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fxrzfr.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fxrzfr.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fxrzfr.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fxrzfr.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /><img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fojdxm.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fojdxm.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fojdxm.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fojdxm.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fojdxm.jpg 1280w\" width=\"600\" height=\"599\" alt=\"\" class=\"\" /></p>\n",
				"date_published": "2021-04-01T21:31:12-05:00",
				"url": "https://bwlng.com/2021/04/01/this-was-a.html"
			},
			
			{
				"id": "http://bowling.micro.blog/2021/02/28/104557.html",
				
				"content_html": "<p>🍕</p>\n<img sizes=\"(min-width: 640) 640, 100vw\" decoding=\"async\" loading=\"lazy\" src=\"https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbndut.jpg\" srcset=\"https://cdn.micro.blog/photos/160x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbndut.jpg 160w, https://cdn.micro.blog/photos/320x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbndut.jpg 320w, https://cdn.micro.blog/photos/640x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbndut.jpg 640w, https://cdn.micro.blog/photos/1280x/https%3A%2F%2Fbowling.micro.blog%2Fuploads%2F2023%2Fbndut.jpg 1280w\" width=\"600\" height=\"337\" alt=\"\" class=\"\" />\n",
				"date_published": "2021-02-28T10:45:57-05:00",
				"url": "https://bwlng.com/2021/02/28/104557.html"
			}
	]
}
