<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Josh Strange</title><link>https://joshstrange.com/</link><description>Engineering manager, founder of GrubBux, and technology enthusiast who codes in his spare time.</description><language>en-us</language><lastBuildDate>Tue, 01 Sep 2026 09:28:23 -0400</lastBuildDate><atom:link href="https://joshstrange.com/index.xml" rel="self" type="application/rss+xml"/><item><title>iRant: Birthday Alerts</title><link>https://joshstrange.com/2026/09/01/irant-birthday-alerts/</link><pubDate>Tue, 01 Sep 2026 09:28:23 -0400</pubDate><guid>https://joshstrange.com/2026/09/01/irant-birthday-alerts/</guid><description>&lt;p&gt;Dear Apple,&lt;/p&gt;&#10;&lt;p&gt;This might be hard to fathom but I have many people in my contacts that I don’t really care about. People from previous phases of my life or just people I’ve fallen out of contact with over the years.&lt;/p&gt;&#10;&lt;p&gt;For years I’ve put up with 9am being the time of day I get hit with a slew of notifications about birthdays. I’ve diligently added birthdays to my contacts in the name of correctness/completeness but now I’m being punished for it with these alerts.&lt;/p&gt;&#10;&lt;p&gt;I never delete contacts (why would I?) and I dislike the idea of having to move the birthday out of the designated field to a custom/notes field so that you won’t alert me on the day of their birthday. My &lt;em&gt;only&lt;/em&gt; option is to turn off notifications for all birthdays which means I don’t get alerts for the people I care about.&lt;/p&gt;&#10;&lt;p&gt;Is it so hard to believe that I might want information but not be alerted to it? That I might want to turn off birthday notifications on a per-contact basis?&lt;/p&gt;&#10;&lt;p&gt;So now I’ve turned off notifications for birthdays globally and rely on custom calendar events for the people I care about. Great system Apple.&lt;/p&gt;&#10;</description></item><item><title>iRant: Find My Teases ETA</title><link>https://joshstrange.com/2025/12/16/irant-find-my-teases-eta/</link><pubDate>Tue, 16 Dec 2025 09:47:37 -0500</pubDate><guid>https://joshstrange.com/2025/12/16/irant-find-my-teases-eta/</guid><description>&lt;p&gt;While I generally enjoy using my various Apple devices, there are some major pain points and endless minor papercuts that I run into every day. Some of these would be understandable, even acceptable, in the v1 of a given service/app but instead of being fixed, they linger for years, untouched by the glacial yearly OS update&lt;a href="#4c40b3cd-df81-4297-a76e-22f18bc2e033"&gt;1&lt;/a&gt; .&lt;/p&gt;&#10;&lt;p&gt;For a while I considered collecting them up to write a detailed blog post on them but instead I’m going to just publish them as I run into them.&lt;/p&gt;&#10;&lt;p&gt;Today’s grievance is a small annoyance in Find My (Apple app/service for tracking people/things). I share my location with a number of friends/family and my #1 use-case is “How far out is person X?” so that I can plan around that. Should I put the dogs up? Should I set the table? Do I have time to finish something before they arrive?&lt;/p&gt;&#10;&lt;p&gt;As with many Apple papercuts, it’s something that &lt;em&gt;sort of/mostly&lt;/em&gt; works. It works just well enough to show you how good it could be, before immediately disappointing you.&lt;/p&gt;&#10;&lt;p&gt;When I tap on a person in Find My it loads a card with more information including how many miles away they are and the estimated drive time. Perfect! It’s exactly what I want, blog post over, all is well!&lt;/p&gt;&#10;&lt;p&gt;Except… They show the drive time for approximately 5 seconds or less then it disappears for no discernible reason.&lt;/p&gt;&#10;&#10;&lt;figure class="video"&gt;&#10; &lt;video controls playsinline preload="metadata" poster="/2025/12/16/irant-find-my-teases-eta/find-my-distance_mp4_hd.original.jpg" width="1080" height="720"&gt;&#10; &lt;source src="https://joshstrange.com/2025/12/16/irant-find-my-teases-eta/find-my-distance.mp4" type="video/mp4"&gt;&#10; &lt;/video&gt;&lt;figcaption&gt;Find My showing a user’s distance (in time) for about 5 second before only showing the distance in miles&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Why Apple? Why do you show me a very helpful “2 min” and then take it away? Why calculate it at all if you are only going to show it once, briefly?&lt;a href="#8fd2a4ad-805a-4e3e-bab1-57a0091df1dc"&gt;2&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;I’d file a Feedback, but I worry they would “fix” it by removing the time estimate entirely. Then again, submitting a Feedback is about as effective as shouting into the void. It would be closed when then next OS shipped with a “please recheck”, or they’d ask for a sample project without reading the report.&lt;/p&gt;&#10;&lt;p&gt;Do better Apple.&lt;/p&gt;&#10;</description></item><item><title>Nightly Postgres Backups via GitHub Actions</title><link>https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/</link><pubDate>Fri, 26 Apr 2024 09:46:36 -0400</pubDate><guid>https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/</guid><description>&lt;p&gt;Recently I wanted to set up nightly backups of my Postgres databases hosted on &lt;a href="https://neon.tech"&gt;Neon.tech&lt;/a&gt;. Yes, they have backups and PITR but I knew I’d feel better if I had backups just in case. My &lt;a href="https://grubbux.com"&gt;business&lt;/a&gt; runs on AWS and so S3 was the obvious destination. I took this as an opportunity to look into what Amazon wants you to use for authenticating instead of having to click the shameful “Other” button (aka “Please just give my access id and secret that I’ve been using for over a decade”).&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery single"&gt;&#10; &lt;a href="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.34.05.png"&gt;&lt;img src="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.34.05_hu_55280978fa7a7f49.webp" width="1002" height="843" alt="AWS appears to discourage the continued use of this method." loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&lt;figcaption&gt;AWS appears to discourage the continued use of this method.&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: Before I get started I want to say I’m sure nothing is this blog post is new or groundbreaking. I just wanted to document how I solved it in case it helps someone in the future.&lt;/p&gt;&#10;&lt;h2 id="aws-setup"&gt;AWS Setup&lt;/h2&gt;&#10;&lt;p&gt;First you’ll need to create an S3 Bucket, this is incredibly straightforward and I left all the defaults as-is except I turned on versioning. I don’t plan to ever upload a file to an existing key but there isn’t a way to easily restrict to add-only (no overwrite) so I figure versions are a safe thing to turn on that might save me one day. Hopefully they are never needed.&lt;/p&gt;&#10;&lt;p&gt;Once you have your S3 bucket let’s go over to the “Management” tab and click on “Create lifecycle rule”. This isn’t strictly necessary but I wanted to move older backups to cheaper S3 storage after a certain amount of time.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.39.16.png"&gt;&lt;img src="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.39.16_hu_47271a7f7e6e95e4.webp" width="1330" height="369" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Here is the rule that I created. I wanted files to move to IA after 30 days and Glacier Instant Retrieval after 60. Also I moved any noncurrent versions to Glacier Instant Retrieval after 30 days.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.41.17.png"&gt;&lt;img src="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.41.17_hu_efd425dfb12159f9.webp" width="833" height="1339" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Once that’s done head over to IAM to add an Identity Provider. This is so that GitHub can assume the role without having to add in any access key/secret to your GitHub Actions Secrets. Go to “Identity Providers” then click “Add provider” and fill in the following:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Select “OpenID Connect”&lt;/li&gt;&#10;&lt;li&gt;Provider Url: &lt;code&gt;https://token.actions.githubusercontent.com&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Audience: &lt;code&gt;sts.amazonaws.com&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.44.37.png"&gt;&lt;img src="https://joshstrange.com/2024/04/26/nightly-postgres-backups-via-github-actions/cleanshot-2024-04-22-at-12.44.37_hu_f98259448463a562.webp" width="825" height="800" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Now that we have that configured we are going to create a new role with a “Custom trust policy”. Edit the one below to match your account and GitHub user/repo.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Principal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Federated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sts:AssumeRoleWithWebIdentity&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Condition&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;StringEquals&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;token.actions.githubusercontent.com:aud&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sts.amazonaws.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;token.actions.githubusercontent.com:sub&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;repo:USERNAME/REPO_NAME:ref:refs/heads/main&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the role is created you’ll want to attach an inline policy, here is the one I used:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nt"&gt;&amp;#34;Version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2012-10-17&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="nt"&gt;&amp;#34;Statement&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="nt"&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="nt"&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:GetObject&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:PutObject&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:AbortMultipartUpload&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:ListMultipartUploadParts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:ListBucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:ListBucketMultipartUploads&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;s3:ListMultiRegionAccessPoints&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="p"&gt;],&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="nt"&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::YOUR_S3_BUCKET/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&#9;&lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::YOUR_S3_BUCKET/&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&#9;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&#9;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that you have your role and identity provider in place we can move on to GitHub.&lt;/p&gt;&#10;&lt;h2 id="github-setup"&gt;GitHub Setup&lt;/h2&gt;&#10;&lt;p&gt;Once all your AWS stuff is done we can setup our GitHub workflow. Copy the following file into the root of your repo at &lt;code&gt;.github/workflows/backup.yml&lt;/code&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Database Backup and Upload&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;33 8 * * *&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run at 8:33am UTC every day (Will be the middle of the night in the US)&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Allows manual triggering from GitHub UI&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backup-and-upload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PREFIX_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# I had multiple databases I wanted backed up and each had a secret like:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PREFIX_2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# &amp;#34;PREFIX_2_BACKUP_DB_CONNECTION&amp;#34;. If you only have 1 DB you don&amp;#39;t need this&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout code&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install PostgreSQL client&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo apt install -y postgresql-common&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; yes &amp;#39;&amp;#39; | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sudo apt-get install -y postgresql-client-16&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set Timestamp&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;echo &amp;#34;TIMESTAMP=$(date -u +&amp;#39;%Y-%m-%d-%H-%M-%S&amp;#39;)&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Dump database&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; /usr/lib/postgresql/16/bin/pg_dump ${{ secrets[format(&amp;#39;{0}_BACKUP_DB_CONNECTION&amp;#39;, matrix.prefix)] }} | gzip &amp;gt; &amp;#34;${TIMESTAMP}.sql.gz&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Configure AWS credentials from Action OIDC&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aws-actions/configure-aws-credentials@v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;aws-region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us-east-1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role-to-assume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;arn:aws:iam::AWS_ACCOUNT_ID:role/ROLE_YOU_CREATED_ABOVE&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;role-session-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GitHubActionSession&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Upload backup to S3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; YEAR_MONTH=$(date -u +&amp;#34;%Y/%m&amp;#34;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws s3 cp &amp;#34;${TIMESTAMP}.sql.gz&amp;#34; s3://YOUR_S3_BUCKET_NAME/${{ matrix.prefix }}/database/${YEAR_MONTH}/&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will run at 8:33am UTC every day and backup each of my databases to S3 in a folder named after the “prefix”. As I explain above, I have a secret per DB and the prefix is used to build the secret name here:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;${{ secrets[format(&amp;#39;{0}_BACKUP_DB_CONNECTION&amp;#39;, matrix.prefix)] }}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you just have 1 database you can hardcode the secret there and remove all the prefix/matrix logic and pass your secret connection string to &lt;code&gt;pg_dump&lt;/code&gt; directly. Just a note, you cannot use a secret value as a matrix value, it will throw an error. My way around this was just specify my “prefix” and then build the secret name later.&lt;/p&gt;&#10;&lt;p&gt;Why “8:33”? Well GitHub says that Action schedules might be delayed if there are a lot of jobs running at that time so choosing to run “on the hour” is probably a contentious time, you could pick any time or have it run on the hour and deal with delays.&lt;/p&gt;&#10;&lt;h3 id="thats-it"&gt;That’s it!&lt;/h3&gt;&#10;&lt;p&gt;Like I said at the top, this isn’t rocket science or anything that hasn’t been done before, I just wanted to document how I accomplished it. I hope you found it useful!&lt;/p&gt;&#10;</description></item><item><title>Kroger’s Digital Struggle</title><link>https://joshstrange.com/2024/02/11/krogers-digital-struggle/</link><pubDate>Sun, 11 Feb 2024 07:30:40 -0500</pubDate><guid>https://joshstrange.com/2024/02/11/krogers-digital-struggle/</guid><description>&lt;p&gt;I’ll start by saying that I know nothing about the grocery store industry. But what I do know is technology and UI/UX. I’ve seen improvements in Kroger’s apps and its website over the past few years but it continues to lag behind its competitors. Not its fellow grocers, of which there seem to be few, but of delivery services like Instacart and Shipt. What makes this so galling is that Kroger is squandering the opportunity in front of them and losing at their own game.&lt;/p&gt;&#10;&lt;p&gt;Kroger has the type of data that would make Instacart/Shipt drool. They have a workforce that puts the gig economy to shame. They could and should be leading the pack when it comes to delivery services but instead, their offerings range from barely adequate to infuriating. I’ve been meaning to write about this for some time, partly to just get all my thoughts in one place and partly so that I can move on instead of continuing to rant about it to my friends. I want better for Kroger, I want them to improve, and I want my shopping experience to be better.&lt;/p&gt;&#10;&lt;p&gt;Before I start I want to add a small note to the developers behind the Kroger app and website: I know you are not solely to blame for anything I’m about to complain about. I know you probably don’t set the priority list, I know you might not get time to work on things you think should be improved, and I know that you’re probably understaffed and unable to effect real change. My annoyance is directed at your managers and the people above them who &lt;strong&gt;do&lt;/strong&gt; have the power to improve things and either don’t care or can’t see the UI/UX mess they have created by proxy.&lt;/p&gt;&#10;&lt;h2 id="infinite-scrolling"&gt;Infinite Scrolling&lt;/h2&gt;&#10;&lt;p&gt;Every long list of data for over a decade has implemented some kind of “automatic pagination” that triggers when you get close or to the end of the current page of data. For some inexplicable reason, Kroger does not auto-load more data. They don’t do it on the web:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.33.55.gif"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.33.55.gif" width="800" height="460" alt="Scrolling to the end of a page of search results" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Scrolling to the end of a page of search results&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;And they don’t do it on mobile:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/rpreplay_final1707604530.2024-02-10-17_36_35.gif"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/rpreplay_final1707604530.2024-02-10-17_36_35.gif" width="884" height="1620" alt="Scrolling to the end of a page of search results" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Scrolling to the end of a page of search results&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;It’s worse on the web, they load 30 results per page and, while I’m glad they reflow the content based on the screen size, it makes it seem like you’ve reached the end of the results when you get to the end of a page.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.40.24.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.40.24_hu_a3fe89e05342f556.webp" width="1053" height="604" alt="Search results always look like you are at the end of the list because of the layout" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Search results always look like you are at the end of the list because of the layout&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;More times than I can count I’ve thought I had seen all there was to see, “That’s so weird, I just bought the thing I’m looking for last week”, before realizing I’m not at the end.&lt;/p&gt;&#10;&lt;h4 id="suggestion"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Ideally, Kroger would just implement infinite scrolling so that when you reached the end they would load the next page but if that’s somehow impossible then they should either only ask for enough results to fill out the row length or any row that wouldn’t be filled out should be hidden until you click “Load More Results”. Of course, infinite scrolling is the clear best answer but with a little development time on the client side only (on the web) you could hide this issue.&lt;/p&gt;&#10;&lt;h2 id="buy-it-again"&gt;Buy it Again&lt;/h2&gt;&#10;&lt;p&gt;In search results, Kroger will promote something you’ve bought in the past (but only one item) to the top of the search results. On mobile this is fine:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/png-image-92ddc29355b6-1-e.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/png-image-92ddc29355b6-1-e_hu_90e86f69ef64d8fe.webp" width="1290" height="2796" alt="“Buy it Again” is the top result in a search on mobile" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;“Buy it Again” is the top result in a search on mobile&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;But on the web, they put it above the search results. Maybe I’m the outlier, maybe decades of training myself to ignore ads and focus on the content are biting me in the posterior, but my eyes skip over the “Buy it Again” block every single time on the web. What’s worse is that the “Buy it Again” item will &lt;strong&gt;not&lt;/strong&gt; also show up in the search results. So if you miss it, you’ll never find it, even after you manually paginate through all the results like a caveman.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.50.11.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-17.50.11_hu_da918c5dfc2cd2f5.webp" width="1433" height="1226" alt="“Buy it Again” looks like an ad on the web" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;“Buy it Again” looks like an ad on the web&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Let’s not even touch on the UI of this page where the results themselves only get ~30% of the page.&lt;/p&gt;&#10;&lt;h4 id="suggestion-1"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Don’t pull out a single item, Kroger also is really bad at guessing what I might want to buy again, just add a banner/ribbon on each item I’ve bought recently. I like that they promote recent items, but put more than just one item up there and make it look like a normal search result.&lt;/p&gt;&#10;&lt;h2 id="more-sizes"&gt;More Sizes&lt;/h2&gt;&#10;&lt;p&gt;After complaining about the lack of this feature for at least a few years I opened the Kroger app one day and saw the most glorious thing “X more sizes” showing in blue text on a search result. Finally, I wouldn’t have to remember “Ok near the top was a small size, I scrolled past the large, but I want to find the medium size”. My search had been for peanut butter, I eagerly clicked on the “X more sizes” and was greeted with the options:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;1 pack&lt;/li&gt;&#10;&lt;li&gt;2 pack&lt;/li&gt;&#10;&lt;li&gt;4 pack&lt;/li&gt;&#10;&lt;li&gt;6 pack&lt;/li&gt;&#10;&lt;li&gt;….&lt;/li&gt;&#10;&lt;li&gt;32 pack&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I’ve never facepalmed so hard in my life. Kroger has a really bad habit of commingling local and ship-only items in the results (which I normally filter out) but it’s just silly to think that someone shopping for pickup/delivery might want an ungodly amount of peanut butter and want to wait 2+ days for it to be shipped. Thankfully Kroger has since fixed this issue. I’m unable to reproduce it today. I only see sane options like this:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.21.04.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.21.04_hu_fd934097f629a76.webp" width="648" height="315" alt="Flavor/size picker for milk" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Flavor/size picker for milk&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Unfortunately, the experience is not consistent, items still show up multiple times in the search results, and some items are still configured as a different item&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.22.47.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.22.47_hu_5482ca202e4e7ba.webp" width="1038" height="1126" alt="Kroger milks that you can switch between and one that’s all by itself" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Kroger milks that you can switch between and one that’s all by itself&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;And then you have items that absolutely should be grouped that aren’t:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.26.12.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.26.12_hu_ddf4a6b8c1170b3d.webp" width="1026" height="1034" alt="All the peanut butter results are their own item" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;All the peanut butter results are their own item&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="suggestion-2"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Continue the work you started and get all items of the same type/brand grouped to make it easier to find what you are looking for, in the size/flavor you want.&lt;/p&gt;&#10;&lt;h2 id="in-store-only"&gt;In-Store Only&lt;/h2&gt;&#10;&lt;p&gt;If you have used the Kroger app then I’m sure you’ve been infuriated by this:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.29.08.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.29.08_hu_1831d86276b75737.webp" width="344" height="443" alt="Plain bagels are only available in-store" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Plain bagels are only available in-store&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;“But Josh!”, I hear you say, “Some items they might want to force you to come into the store for, or maybe their bagel selection/availability varies too much!”. Well, your first point is just silly, the shopper (Instacart or Kroger employee) is in the freaking store, but your second point is valid. Or rather it would be except they offer other bagels, from the bakery, for delivery:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.30.07.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.30.07_hu_4cf60e68652f0f16.webp" width="1029" height="888" alt="Other bagel flavors are available for pickup/delivery" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Other bagel flavors are available for pickup/delivery&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;There is simply no rhyme or reason as to what is or is not available for pickup/delivery vs in-store.&lt;/p&gt;&#10;&lt;h4 id="suggestion-3"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Make it make sense Kroger. I assume this is a data entry issue though I have observed it happening at Kroger stores that are hundreds of miles apart (specifically the bagel issue) but maybe it’s some flag set on plain bagels everywhere. It’s not just a bagel issue, I randomly see it on other items for which it makes no sense, like shelf-stable items that I can only get by going into the store.&lt;/p&gt;&#10;&lt;h2 id="item-not-included"&gt;Item Not Included&lt;/h2&gt;&#10;&lt;p&gt;This goes hand in hand with the section above. For some reason when I go to checkout, it will tell me that some number of the items I have in my cart are not available.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4893-e.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4893-e_hu_583785af41185e28.webp" width="1290" height="2796" alt="Certain delivery times have items not available" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Certain delivery times have items not available&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;What’s even more confusing is sometimes the “x item not included” message only shows up for certain time ranges. It’s completely random. It has nothing to do with things like when the deli or meat counter is open, it will sometimes just be the first time slot, it will sometimes be a random time slot in the middle of the list of options, there is no consistency at all.&lt;/p&gt;&#10;&lt;p&gt;Then, once you’ve removed the item (let’s assume it wasn’t available at any of the time slots), and you click “Continue” you are sometimes greeted with this screen/message:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4894e.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4894e_hu_d03c755d8c0783a3.webp" width="1290" height="2796" alt="Different/new items are now not available" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Different/new items are now not available&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;It’s bad enough they can’t tell you while the item is still in your cart but having 2 places where they tell you something isn’t available just makes no sense and is frustrating.&lt;/p&gt;&#10;&lt;p&gt;Also, look at those corners, we’ve got 2 border radiuses/corners. I assume it’s because they need to be able to put that “not available” message above the item sometimes but please line up the border radius of these 2 divs.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4894e2.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_4894e2_hu_f8f9fb05f63e451a.webp" width="1290" height="1268" alt="Double corner" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Double corner&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="suggestion-4"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Ideally, tell me it’s not available in my cart and/or when I try to add an item. Also, explain why it’s not available. From everything I can see there is no reason, I don’t know if this is a bug or what’s going on. At a minimum tell me all at once if things aren’t available.&lt;/p&gt;&#10;&lt;h2 id="substitutions"&gt;Substitutions&lt;/h2&gt;&#10;&lt;p&gt;Speaking of things not being available, let’s talk about how Kroger handles substitutions. A text field. A text field with only enough room for an OG tweet. A text field that only accepts a subset of characters. I hope you &lt;del&gt;don’t&lt;/del&gt; do not use contractions to save some of your precious space:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.54.54.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.54.54_hu_12c627275f02ca8c.webp" width="732" height="341" alt="Limited substitution controls with limited text entry" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Limited substitution controls with limited text entry&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The best part? Yeah, anything you put in there isn’t saved for your next order. You have to type it in manually, every, single, time. This is one place where Instacart/Shipt blow Kroger out of the water. They let you pick a specific item as a replacement. However, I wouldn’t waste your time with this field, at least if your order is being fulfilled by Instacart, because the shoppers don’t read it. I can’t remember if the pickup (which is handled by Kroger) pickers respected that field or not but I often forgot to fill it out because it’s not prominent and since it doesn’t save what you put in, it feels like a waste.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.58.04.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-18.58.04_hu_e855b66828c046fb.webp" width="770" height="211" alt="The only place they suggest you set a backup item" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;The only place they suggest you set a backup item&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I assume Kroger employees are better at this since I’ve had better luck with Kroger Pickup vs Kroger Delivery (fulfilled by Instacart) but Instacart shoppers don’t seem to be able to do basic substitutions. I’ve had 16 ounces of something substituted for the 32 ounces I asked for. You’re telling me they had no 32oz items and only one 16oz item? But I digress, I fully expect that when Kroger Blue Van delivery comes to my area this will improve.&lt;/p&gt;&#10;&lt;h4 id="suggestion-5"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Let me pick a specific item, let me pick multiple fallback items, and save my selections/text. This is a gold mine of data that Kroger is leaving on the table. Once you save this you can start using it to suggest substitutions to other users. Instacart pushes you hard to pre-select substitutions, it saves everyone time and means I don’t have to be “on call” while my shopper is shopping.&lt;/p&gt;&#10;&lt;h2 id="low-image-resolution"&gt;Low Image Resolution&lt;/h2&gt;&#10;&lt;p&gt;While gathering data for this section I found something that really annoys me. It’s not, always, that the images are low resolution but rather on mobile they don’t provide images at the full resolution. Here is the same item zoomed in on mobile (pinch/zoom) and on the web (hover to zoom):&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_5fefb9f8de08-1-e.jpeg"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_5fefb9f8de08-1-e_hu_46c5685187939c86.webp" width="1290" height="1689" alt="Zoomed-in image on mobile" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Zoomed-in image on mobile&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-19.05.07.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-19.05.07_hu_4728a36a994ccbba.webp" width="1162" height="457" alt="Zoomed-in image on the web" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Zoomed-in image on the web&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I can read the serving size and servings per container on the web but it’s incredibly hard to read on mobile. What about the nutritional facts? Don’t they have a section for this? Yes they do, this was a great addition whenever they added it, it wasn’t always there. Let’s take a look at the nutritional facts:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_2259773201f5-1.jpeg"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_2259773201f5-1_hu_429dc030bbb483e7.webp" width="1290" height="903" alt="Nutritional Facts on mobile" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Nutritional Facts on mobile&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;They do show servings per container for some items but some are just blank leaving you at the mercy of the 1-2 low-quality pictures they bothered uploading.&lt;/p&gt;&#10;&lt;p&gt;This is where I start to pull my hair out. Kroger, you have full access to the items you are selling. You could take high-quality pictures of the items and you could make it easy to see how big something is. Maybe I’m dumb but on more than one occasion I’ve over or under-bought what I wanted because the picture looked bigger/smaller. Yes, yes, they often list the weight/fluid ounces but that means next to nothing to me. I regularly have to look at all the sizes available (which are often not grouped nicely) and then translate to what I probably want. “I want the biggest one”, “I want the medium one”, etc. I often depend on things like “2 cups” on the package for something like shredded cheese to know how much I need but sometimes that’s too blurry to read in the image.&lt;/p&gt;&#10;&lt;h4 id="suggestion-6"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Take better pictures, take more pictures, and expose the full-resolution images in the mobile app. You could not do worse than what you have now. Set up a white sheet, have one of the 15-16-year-olds you employ use their iPhone to take 4 pictures of each item, and use those. It would be a massive step up from what you have right now.&lt;/p&gt;&#10;&lt;h2 id="similar-items"&gt;Similar Items&lt;/h2&gt;&#10;&lt;p&gt;It seems that Kroger has removed this widget that used to show on the item detail page in the mobile app. It still shows up on the web sometimes but it’s hit or miss. Sometimes they show related/similar items, sometimes they show “healthier options”, and most of the time they show nothing. To be honest, when these did show up on mobile the suggestions were horrible. It wasn’t personalized, it would show items that were only barely similar, and suffered from the lack of groups for size/flavor. It doesn’t help to show me something I do want if I have to go searching to find the right size.&lt;/p&gt;&#10;&lt;p&gt;I just don’t understand why Kroger is so bad at this. The only usable tools in the Kroger app are search, savings, and the weekly ad (more on that later). The “Shop” tab that shows you a list of departments will dash your hopes immediately. There appears to be no ordering or reasoning to the list of items it presents. It could have subsections under “Meat &amp;amp; Seafood” like “Beef”, “Chicken”, etc but instead you just get a list of items in a random order. It’s completely useless, you are always better off searching.&lt;/p&gt;&#10;&lt;p&gt;While searching is the best way to interact, it still sucks. You are forced to try to come up with a search term that will catch all the results you want but that means you need to be incredibly vague which results in getting tons of semi-related items. Even something more specific like “Sausage roll” returns hamburger buns and frozen omelets (that don’t have sausage in them). You have to constantly wonder if you are missing items and/or you have to slog through horrible search results.&lt;/p&gt;&#10;&lt;p&gt;Kroger, you have already come up with a solution to this. It’s called aisles/shelves. You already know (or you better know) where items are in your stores. Why can’t I see “Items around this item” physically in the store? Why can’t I go to the “Mexican” section, why can I go to the “Italian” section, why am I forced to deal with your god-awful search?&lt;/p&gt;&#10;&lt;p&gt;Here’s the thing, this failure on Kroger’s part is almost certainly leaving a ton of money on the table. If you come into the store they can advertise to you via end caps, signs, and by pairing related items next to each other. If you go to where the pizza crusts are in the store you will find pizza sauce and pepperonis right next to it. It wouldn’t be practical to put cheese near it in a physical store but you absolutely can do that in a digital store. Make 1 button I can click to get pizza crust, pizza sauce, cheese, and pepperonis. Have little (+) buttons for other toppings (tomatoes, peppers, olives, etc). Make it easy to buy everything you need for a meal.&lt;/p&gt;&#10;&lt;h4 id="suggestion-7"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Let me look at aisles, let me look at a list of items next to this item in the physical store, let me price compare by size within a product type, let me see all the flavors, and make it easy to buy items that are connected/related.&lt;/p&gt;&#10;&lt;h2 id="change-cart-type"&gt;Change Cart Type&lt;/h2&gt;&#10;&lt;p&gt;I don’t think I’ve ever run afoul of this on mobile but on the web when I want to see what’s in my cart I click the cart icon in the top right, then I have to click again to see the list of items. Kroger will show you a few images of what’s in your cart but you can’t click on them, you need to click on “Show All Items”. The problem is the “Change to Pickup” button is just as prominent and my cursor is drawn to that button like moths to a flame. I don’t know why, again I could just be dumb, but I’ve clicked that button more times than I can count.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-19.37.27.png"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/cleanshot-2024-02-10-at-19.37.27_hu_b9b6af351b13c887.webp" width="761" height="347" alt="Change to Pickup is too enticing" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Change to Pickup is too enticing&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;There is no confirmation, there is no undo, and then sometimes you get notices that some things aren’t available for pickup (which also makes no sense to me), and then you have to convert your cart back and hope nothing was lost in the process.&lt;/p&gt;&#10;&lt;h4 id="suggestion-8"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;That button does not need to be that big/obvious and it should have a confirmation step. Furthermore, I only have 1 cart I’m adding items to at a time and it’s always a delivery cart, why can’t you just take me to the “Show All Items” screen by default when I click on the cart with some UI at the top to see my other carts (if I have any).&lt;/p&gt;&#10;&lt;h2 id="you-can-still-add-items-to-your-order"&gt;You can still add items to your order&lt;/h2&gt;&#10;&lt;p&gt;Kroger seems to have changed this in the last few weeks, maybe longer, I had no idea it had changed until I randomly tried it again. After you place an order for delivery or pickup you get a text like this:&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;“Your Kroger Delivery by Instacart is confirmed. And remember, you can still add items to your order.”&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Up until recently, that was not true unless you had placed an order for over a day in the future. It didn’t matter that your order might still be hours out and the Instacart shopper hadn’t even been chosen, let alone started shopping, your order was locked and you were out of luck if you wanted to make changes. Just last week I forgot something and saw the “Edit order” button in the UI wasn’t greyed out like normal and got very excited. “Finally!” I said, I added the extra item, saved my order, and moved on. What I didn’t realize (and if there was UI for this it wasn’t clear) is that this caused my order to get moved out by about 2 hours. I made the edit minutes after initially placing the order. It was far from the end of the world but could have ruined plans if I wasn’t going to be home or needed the items to make something for an event later that night.&lt;/p&gt;&#10;&lt;h4 id="suggestion-9"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Let people edit their orders until shopping starts, or at least until a shopper has been picked. For pickup orders don’t cut off edits at midnight, cut it off once someone starts to pick the order. And if you are going to shift the delivery time make sure that’s in big bold letters.&lt;/p&gt;&#10;&lt;h2 id="weekly-ads"&gt;Weekly Ads&lt;/h2&gt;&#10;&lt;p&gt;Here is a place I want to praise Kroger… well I also have a complaint &lt;strong&gt;but&lt;/strong&gt; I do want to applaud them for improving this. It used to be essentially a scan of the mailer they sent out where you could click on items (think &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"&gt;image map&lt;/a&gt;). It wasn’t great, it was hard to read on mobile, and originally it only let you “add to your list” which is a separate concept, it’s just a shopping list, not related to delivery/pickup. Somewhat recently they converted that to a list of items in the app you can scroll through. Perfect right? Right?&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_7865.jpg"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/img_7865_hu_57520c08a5c4ccd6.webp" width="1290" height="2265" alt="Weekly Ad" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Weekly Ad&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Well, when I scrolled through I thought “Huh, seems like a lot fewer things on verses the old image style weekly ad”. Then, by accident, I learned there were more than just 2 items in each “section”:&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/11/krogers-digital-struggle/rpreplay_final1707612844.2024-02-10-19_57_19.gif"&gt;&lt;img src="https://joshstrange.com/2024/02/11/krogers-digital-struggle/rpreplay_final1707612844.2024-02-10-19_57_19.gif" width="886" height="1632" alt="Hidden Scroll" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Hidden Scroll&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="suggestion-10"&gt;Suggestion&lt;/h4&gt;&#10;&lt;p&gt;Make the tiles smaller or larger so that 1 tile is slightly off-screen so it’s clear there are more items if you scroll horizontally.&lt;/p&gt;&#10;&lt;h2 id="whats-kroger-doing-right"&gt;What’s Kroger doing right?&lt;/h2&gt;&#10;&lt;p&gt;I’ve spent this whole post beating up on their app and website so why don’t I go use Instacart or Shipt instead? It’s simple: cost. These 3rd party delivery services add a significant upcharge (25-30%+) and you can’t use coupons. Go create the a cart on Kroger and then add the same items on a 3rd party delivery service, I think you’ll be shocked at how much you are overpaying.&lt;/p&gt;&#10;&lt;p&gt;Coupons are the one part of the Kroger app that is almost perfect. The filtering is pretty decent, and necessary unless you want to scroll through pages of alcohol and other things you don’t care about. The ability to clip coupons and find eligible items is almost perfect. Sometimes the list of eligible items is very long and in almost random order. You don’t know if the flavor/etc you want is further down the list or not eligible. On the checkout screen, the app will warn you if you haven’t clipped all the applicable coupons for items in your cart. Major props to Kroger on this.&lt;/p&gt;&#10;&lt;p&gt;Kroger Pickup is the other bright spot which makes me optimistic for Kroger Blue Van delivery. I know that outsourcing delivery to Instacart is just a stepping stone, a necessary evil, while Kroger rolls out its alternative but I cannot wait for actual Kroger employees to pick my order. My pickup orders have almost always left me delighted with their substitutions and their policy of always giving you the lower price even if the substitution is more expensive is nothing short of amazing, which maybe says more about what I’m conditioned to expect from massive companies.&lt;/p&gt;&#10;&lt;p&gt;I often wonder if Kroger could find a nice middle-ground while they roll out the Blue Van delivery. If they could have their Kroger Pickup employees pick all the delivery orders and then just pay an Uber-like service to do the delivery aspect. I’m sure it’s not worth it for them now as they race towards replacing Instacart but surely it would have been better for them as a brand and saved them money.&lt;/p&gt;&#10;&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p&gt;I want Kroger to improve, I want them to succeed, and I want to not go crazy when shopping for groceries. Kroger has all this data at their fingertips, in most cases, they already have what they need and in the other cases they could get what they need easily. Instacart and Shipt came in and showed you how to do it, shameless copy them, they can never beat you at this game, they don’t have the products, they are an over-the-top service that is dependent on you. You can undercut them with ease (both in price and data) and you should.&lt;/p&gt;&#10;</description></item><item><title>The Use Case for Mac Virtual Display</title><link>https://joshstrange.com/2024/02/10/the-use-case-for-mac-virtual-display/</link><pubDate>Sat, 10 Feb 2024 10:15:47 -0500</pubDate><guid>https://joshstrange.com/2024/02/10/the-use-case-for-mac-virtual-display/</guid><description>&lt;p&gt;I’ll be honest, I still think Mac Virtual Display (MVD) for the Apple Vision Pro (AVP) is lackluster. Though I think I have a better understanding of why some reviewers or tech bloggers/enthusiasts have sung it’s praises. I also think they will not be waxing quite as poetic after a week or two of use.&lt;/p&gt;&#10;&lt;p&gt;I’ve written before about &lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/"&gt;trying to code on the AVP&lt;/a&gt; and &lt;a href="https://joshstrange.com/2024/02/05/apple-vision-pro-or-4k/"&gt;why I’m probably going to return it&lt;/a&gt; and both times I wrote off MVD as being blurry and a massive step down from using my external monitors. That sentiment hasn’t changed and I stand by all of what I’ve said. However there is a use case that makes at least some sense. When all you have is your MBP’s built-in display and especially if you are on the 14″.&lt;/p&gt;&#10;&lt;p&gt;Today I spent an hour or so on my couch using the AVP with MVD and I kind of get it. I think that most of the people I read/heard who spoke highly of MVD were traveling while using it. Compared to just your laptop monitor I can understand why some people might prefer the MVD. Let me be clear though, I still don’t. I thought maybe the ergonomics of looking forward instead of down at the screen would make up for the blurriness, it doesn’t. I thought I’d try what Marco Arment suggested on ATP episode &lt;a href="https://atp.fm/573"&gt;573: Look at it Harder&lt;/a&gt;, making the resolution 1080 instead of the default. That helps with being able to read text some but it’s still not anywhere near as sharp as a native AVP app and you sacrifice a ton of screen space when you do that.&lt;/p&gt;&#10;&lt;p&gt;I tried writing some of this blog post using MVD but after taking off the AVP and just using the MBP screen (granted, mine is 16″) I never went back. In fact after 10 minutes or so of using my MBP I got up and moved to my office because, as I’ve said multiple times before, that’s where I’m most productive. I don’t even have the nicest external monitors, mine are 27″ 2K (1440×2560) so they don’t even hit Apple’s retina targets. For retina I’d need to be at 27″ 5K but I can’t bring myself to spend $4.5K (Apple Studio Display) to replace the monitors at only 1 of the 2 locations I work at. Yes, there are &lt;a href="https://forums.macrumors.com/threads/the-complete-list-of-27-5k-displays.2390249/"&gt;cheaper options&lt;/a&gt; I could roll the dice on, but even at the low end (~$500/ea) I’m looking at close to $3K to replace the monitors at both of my offices. All that said, my 2K monitors blow away the MVD, it’s no contest.&lt;/p&gt;&#10;&lt;p&gt;I’m not the first to call the AVP an “iPad you wear on your head” but it’s an apt description. This is an impressive hardware device that is limited by the software allowed to run on it. I think a lot of people, myself included, thought that MVD would be an “escape hatch” of sorts. That whatever else happened, we could reach for the full power of our MBP at any time. I’m here to tell you that it’s a pipedream as it stands today. I would have gladly paid $300 for the “&lt;a href="https://www.macstories.net/linked/the-apple-vision-pro-developer-strap/"&gt;Developer Strap&lt;/a&gt;” and tethered myself to the MBP if it allowed even 1 crisp virtual display but that’s not supported and honestly probably isn’t even possible given that it uses USB2 speeds (come on Apple, that’s just ridiculous in 2024). Even if the price weren’t an issue, it is, the extra weight of carrying around the AVP and your MBP disqualifies it given the lack of sharpness. Speaking of carrying the AVP, I haven’t even talked much about the official Apple carrying case, it’s a huge miss for me. It looks wrinkled all the time, is oversized, and not anywhere close to being worth $200.&lt;/p&gt;&#10;&lt;p&gt;So my AVP is going to go back to Apple, hopefully it finds its way to someone who can be productive on iPadOS (and thus productive in visionOS). It’s an amazing technical feat, the virtual environments are stunning, Apple has provided the best VR/AR UI/UX currently available (though it still has shortcomings), and the passthrough is the best available (still too noisy/blurry). I have zero doubt I will own a future version of this device but the Apple Vision Pro “Series 0” is not for me. I just don’t get enough utility out of it for the cost. I can’t wait to see what Apple and developers do with this platform going forward, I know I’ll be back.&lt;/p&gt;&#10;</description></item><item><title>Apple Vision Pro or $4K?</title><link>https://joshstrange.com/2024/02/05/apple-vision-pro-or-4k/</link><pubDate>Mon, 05 Feb 2024 21:03:52 -0500</pubDate><guid>https://joshstrange.com/2024/02/05/apple-vision-pro-or-4k/</guid><description>&lt;p&gt;The Apple Vision Pro (AVP) is quite amazing. The picture quality is the best I’ve experienced, the UI/UX is top notch in typical Apple fashion, but I don’t find myself craving it. Sure the first few days it was super cool but I think I enjoyed blogging about it almost as much as using it, if not more. If you want the best TV (that only you can watch) then the AVP might be for you, past that it really feels like an iPad instead of a Mac. That’s not surprising, Apple never claimed it was like a Mac and the OS and apps are iPad-like (if not iPad apps themselves).&lt;/p&gt;&#10;&lt;p&gt;I never “got” the iPad. I mean, I understand it as a content consumption device and it does a great job at that but it was never a productivity device for me. It’s an amazing platform, my day job uses it extensively (POS software, we’re hiring if you are interested in using Vue/TS and/or PHP, &lt;a href="mailto:josh@joshstrange.com"&gt;email me&lt;/a&gt;) and so does my own company (&lt;a href="https://grubbux.com/"&gt;GrubBux&lt;/a&gt;, food festival software). But I’ve always seen it as dependable, reliable hardware that is great to build on top of. I’ve never seen it as something I’d use for anything but to watch a movie, tv show, or maybe play a game. I know some people can (or force themselves to) do all their work on it but that’s not me and could never be me. I write code and need macOS to be most productive, everything else feels limiting.&lt;/p&gt;&#10;&lt;p&gt;If I could draw I’d have a comic here of a MBP singing to an AVP (and iPad/iPhone) “&lt;a href="https://youtu.be/WO23WBji_Z0?t=10"&gt;Anything you can do I can do better&lt;/a&gt;“.&lt;/p&gt;&#10;&lt;p&gt;And that’s what visionOS and the AVP is: limiting. I tried to use to write a very simple feature (using Mac Virtual Display) and after a deploy to my dev environment one of my monitoring systems threw an alert. It was nothing to do with the AVP but within 5 minutes of trying to investigate the error I took off the AVP to work on my Mac instead. I know some of you might be yelling at me right now that using Mac Virtual Display (MVD) was my mistake, I should have been using a visionOS app instead. To those people I just have to shake my head and wonder what world they are living in.&lt;/p&gt;&#10;&lt;p&gt;Yes, there are apps like &lt;a href="https://apps.apple.com/us/app/runestone-text-editor/id1548193893?platform=appleVisionPro"&gt;Runestone&lt;/a&gt;, which look cool, but I’m not going to store my code in iCloud or mount a network share. Even if I was willing to do that, Runestone is not an IDE. I don’t think JetBrains IDEA is coming to AVP anytime soon and even if it was I can’t work without a terminal and full access to the underlying machine. And no, I don’t want to work on some remote machine. I’m incredibly productive on my MBP with 3 external monitors, I can’t even code in the AVP without my Mac. My main interest in the AVP was as an external monitor replacement, &lt;a href="https://joshstrange.com/2024/02/03/apple-vision-pro-first-impressions/"&gt;as I said in my first post&lt;/a&gt; and when I talked about writing code &lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/"&gt;in my second post&lt;/a&gt;. The AVP might be better than just a MBP screen but it’s not better than my monitors by a long shot.&lt;/p&gt;&#10;&lt;p&gt;So the question becomes, is $4K worth it for an occasional monitor (if I’m not in one of the 2 places I have external monitors) and as a TV or lackluster gaming device? For me I think the answer is no. I tried the 3D games and they felt gimmicky after a while, just like in my Meta Quest 2 (MQ2), and I can play 2D games on literally anything else. In fact I prefer to play games on my Steam Deck or Xbox. &lt;a href="https://apps.apple.com/us/app/dead-cells/id1389752090"&gt;Dead Cells&lt;/a&gt; was fun to play on a massive floating screen but I could also plug my Steam Deck into my TV. You can only make a screen so big before you have to pan your head/eyes to see it all. My 65″ TV is just fine and if it wasn’t, I could go much larger without coming close to the AVP costs. I could also enjoy the TV with friends and family.&lt;/p&gt;&#10;&lt;p&gt;A lot of my AVP use over the past few days has been more “work” than anything and not my real “work”. Work in the sense that I was testing out the device, putting it through its paces, forcing myself to explore every nook and cranny, forcing myself to use it for everything I could. That’s fun as a nerd but once I was done testing out things I just wanted to relax and play a game or watch a show. The games I want to play aren’t on visionOS and while I can watch a show in the AVP, I don’t think it beats my TV. I know that sounds crazy but hear me out.&lt;/p&gt;&#10;&lt;p&gt;If I get a text/email/etc while I’m watching TV I can just pick up my phone and deal with it. In the AVP it’s a much bigger ordeal, mostly because of text input (or cleaning up voice input). A paired keyboard might make it better but it’s not going to beat the ease of picking up my phone, responding, and setting it back down. Is the AVP more useful than my phone? Maybe, in some contexts but here’s the thing, it’s never better than my Mac. If I’m in my living room and an alert or a support ticket come through that I need to deal with then I get up and walk into my office. I had hoped that maybe MVD would be good enough that I could just do it from the couch but it’s just not.&lt;/p&gt;&#10;&lt;p&gt;The downsides of wearing the AVP just don’t outweigh the upsides. If I was space-limited, didn’t have an office at home, or similar then maybe I’d come down on this differently but that’s not my life. If the AVP let me jump onto my Mac from anywhere in my house and be even 90% as productive as going to my office then maybe. But I can only do that if my Mac is nearby (if only for the bluetooth mouse/keyboard to work) and even at my desk I feel limited by the AVP.&lt;/p&gt;&#10;&lt;p&gt;I have no doubt I’ll buy a future version of this product. If the price was much lower I’d keep it. If the passthrough wasn’t so blurry/grainy (it’s light years better than the MQ2 but not perfect) I’d keep it. If I was planning (or had the time) to develop for this platform I might be able to convince myself to keep it. Leaving windows floating in your house and seeing them as you walk by is neat. Cooking in my kitchen surrounded by screen was awesome. But only seeing the world through these cameras isn’t good enough. Maybe when they have their “retina” moment I’ll come back. You can’t see pixels in this device (the screen door effect of the MQ2 was bad) but the cameras can’t capture the world around you well enough to make you forgot you’re wearing the AVP.&lt;/p&gt;&#10;&lt;p&gt;I’m going to hold off making my decision for a few more days but right now I’m leaning heavily towards returning it. I have a lot better uses of $4K, including putting it back in a HYSA. If anyone can pull this off it’s Apple but either MVD is going to have to get way better and/or the cameras need to get better (or they need to make real “glasses”). I know this is really just a dev kit in disguise to prepare for a future when Apple can deliver on true AR, the same way we’ve had AR/VRKit for years as Apple prepared for this moment. I know it was the right decision for Apple to ship this as-is, I applaud them for it. Developers need time to get used to it as a platform and understand how to take full advantage. I’ll definitely be back for true AR and I might come back if passthrough gets better. I see the promise of this, I just don’t think it’s worth it for me right now.&lt;/p&gt;&#10;</description></item><item><title>Apple Vision Pro: Writing Code</title><link>https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/</link><pubDate>Sun, 04 Feb 2024 12:39:44 -0500</pubDate><guid>https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/</guid><description>&lt;p&gt;So yesterday I posted about my &lt;a href="https://joshstrange.com/2024/02/03/apple-vision-pro-first-impressions/"&gt;first impressions on the Apple Vision Pro&lt;/a&gt; (AVP) and I wanted to expand a bit on using the Mac Virtual Display feature. As I said yesterday, I do not see this replacing my external monitors. In part because you only get 1 monitors in AVP and also because the text that you aren’t actively focusing on is slightly blurry. There is no denying that this is leaps and bounds better than the Meta Quest 2 (MQ2), where text was incredibly difficult to read unless it was massive. Even then the text was still fuzzy and hurt my head to read for long periods of time. In contrast, AVP’s text is perfectly clear when you are looking at it but in your periphery it’s blurry and sometimes you need to move your head/eyes slightly to bring it into focus.&lt;/p&gt;&#10;&lt;p&gt;I think that given the option of just my MBP screen or the AVP I might pick the AVP (like on an airplane or in a hotel room) but it’d be close. That said, the fact that it’s so close is rather astounding. I’ve used Meta Remote Desktop and it wasn’t usable for me, this is usable, just not as good as 3-4 external monitors. In real life there is no lag, there is no “smearing” as you move your head, and while I think I could get used to that, why would I?&lt;/p&gt;&#10;&lt;p&gt;I still want to try this out on the couch and compare it to using the laptop directly (my laptop is docked in clamshell mode 99% of the time, it’s a portable desktop for me) to see if I lean towards using the AVP more. I rarely use my laptop on the couch because getting the right angle for my keyboard/screen is difficult while also remaining comfortable. The AVP might change that calculus since I could have the keyboard on my lap (either wireless keyboard/trackpad, &lt;a href="https://www.amazon.com/dp/B01N9D63YE"&gt;I bought this to try out&lt;/a&gt;, or using the laptop keyboard/trackpad directly) and then have the screen hovering in front of my eyes. The problem is when I’m at my desk the desire to take off the AVP and do “real work” is strong.&lt;/p&gt;&#10;&lt;p&gt;I shot some video of using the AVP at my desk with my computer monitor hovering in front of me surrounded by AVP apps and I could see that being nice… if I didn’t have nearly the same screen real estate sitting right there ready to be used. The default screen recording built into the AVP is not representative (I think I’ve read it records in 720p?) but there is a way to record better videos &lt;a href="https://developer.apple.com/documentation/visionos/capturing-screenshots-and-video-from-your-apple-vision-pro-for-2d-viewing"&gt;by pairing your AVP with your Mac and recording it in Reality Composer Pro&lt;/a&gt;. Below I have 2 screenshots I pulled from a video showing text on my physical computer screen while moving my head and while holding still. Note: this is NOT using Mac Virtual Display, this is looking directly at a real physical screen. I think this best shows the “smearing” I mentioned above for all passthrough video.&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery single"&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h13m20s193.png"&gt;&lt;img src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h13m20s193_hu_eacd34cd4c7fc61.webp" width="1600" height="900" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery single"&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h13m55s529.png"&gt;&lt;img src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h13m55s529_hu_e7e6bbd7aa1896bc.webp" width="1600" height="900" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;That’s how passthrough works in the AVP. Moving smears the video a bit but then once you stop it’s pretty clear. The same thing happens when using AVP apps or Mac Virtual Display (to a lesser degree though). Capturing video of the Mac Virtual Display from the AVP I couldn’t reproduce the slight blur that happens in-headset.&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery single"&gt;&#10; &lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h27m36s890.png"&gt;&lt;img src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/vlcsnap-2024-02-04-11h27m36s890_hu_a25bf4636ec83e0a.webp" width="1600" height="900" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I found the immersion you can active by turning the Digital Crown to be helpful since I didn’t see the real world and so the smearing is much less noticeable. Make sure you watch this in the highest quality version you can select from the video player.&lt;/p&gt;&#10;&#10;&lt;figure class="video"&gt;&#10; &lt;video controls playsinline preload="metadata" poster="/2024/02/04/apple-vision-pro-writing-code/output_video_mp4_vp9_2160p.original.jpg" width="3840" height="2160"&gt;&#10; &lt;source src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/output_video.mp4" type="video/mp4; codecs=&amp;#34;hvc1.2.4.L150.B0, mp4a.40.2&amp;#34;"&gt;&#10; &lt;source src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/output_video_vp9_2160p.mp4" type="video/mp4; codecs=&amp;#34;vp09.00.51.08, mp4a.40.2&amp;#34;"&gt;&#10; &lt;/video&gt;&lt;figcaption&gt;Mac Virtual Display in AVP (with other AVP apps around it)&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I think this video makes it look slightly better than it does in the AVP. Mostly because all the text is clear everywhere when I rewatch the video but while I’m wearing the AVP I feel like it’s only clear where I’m actively looking. Either way I think the talk about having a 4K display in AVP with Mac Virtual Display was a bit off the mark. My physical monitors are 2K (1440×2560, non-retina) and they look sharper than this.&lt;/p&gt;&#10;&lt;p&gt;But it really comes down to if you have multiple monitors in all the places you regularly work. I do, I have 2 offices set up the same way (3 monitors, 2 vertical flanking 1 horizontal) at my house and my parents house which makes it easy to go visit for long periods of time. If I didn’t have that setup at their house I could see myself using this while I was there. The AVP would probably be great for airplanes (though I rarely fly) and hotels. I’d also be interested in trying this in the car (as a passenger, &lt;a href="https://twitter.com/blakestonks/status/1753932112757924034"&gt;I’m not a moron&lt;/a&gt;) as well in Travel Mode which I have not had a chance to try yet.&lt;/p&gt;&#10;&lt;p&gt;I’m still undecided on if I’m going to keep the AVP or return it (I have 11 days to decide). There are just somethings I want to do regularly that I can’t do in my AVP, like playing my Steam Deck. I do want to try playing games on the Mac Virtual Display, I have played a few 2D and 3D games in the AVP (using hand tracking) and they are cool but I can’t decide if they are just cool tech demos or if I’d actually prefer playing them over something else. With &lt;a href="https://developer.apple.com/news/?id=f1v8pyay"&gt;Apple opening up the App Store to steaming game services&lt;/a&gt; I could see myself playing Xbox games on a large 2D screen with a paired controller. Ideally I could stream from my Xbox Series X to the AVP since I’m not a huge fan of streaming game services with their current limitations but for now I can try out Xbox’s &lt;a href="https://support.xbox.com/en-US/help/games-apps/cloud-gaming/setup-cloud-gaming-apple"&gt;current web-based version&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;A small update not related to the Mac Virtual Display, I wore my AVP around the house a lot more yesterday. I did some cooking (or rather prep for cooking I’m doing today) and it was super cool to have screens floating all around me in my kitchen. I had a podcast playing on &lt;a href="https://apps.apple.com/us/app/overcast/id888422857?ls=1"&gt;Overcast&lt;/a&gt;, I had a &lt;a href="https://apps.apple.com/us/app/widgetsmith/id1523682319"&gt;Widgetsmith&lt;/a&gt; clock hovering over my fridge, &lt;a href="https://apps.apple.com/us/app/paprika-recipe-manager-3/id1303222868?ls=1"&gt;Paprika&lt;/a&gt; was hovering over my stove, Safari was open to a HN discussion I was following, and I had iMessage and Discord over my cabinets. Walking around and looking down at the counter made me feel the weight of the AVP a little more but it didn’t cross over into “uncomfortable” territory. I plan to wear the AVP as much as I can over the next week to really force myself to decide if I actually get valuable usage out of it or if it’s still too early and I’m better off returning it.&lt;/p&gt;&#10;&lt;p&gt;Apple has created something truly magical and it’s the best version of this type of device I’ve ever used. If Apple continues to iterate on this product I have no doubt it will be able to replace my external monitors in the future. When Apple commits to a product line they create truly amazing things, hopefully they won’t lose interest in VR/AR, I’m sorry “Spatial Computing”.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/cleanshot-2024-02-04-at-12.29.31.png"&gt;&lt;img src="https://joshstrange.com/2024/02/04/apple-vision-pro-writing-code/cleanshot-2024-02-04-at-12.29.31_hu_5b4fc9e339b9242e.webp" width="745" height="92" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Apple Vision Pro: First Impressions</title><link>https://joshstrange.com/2024/02/03/apple-vision-pro-first-impressions/</link><pubDate>Sat, 03 Feb 2024 12:58:00 -0500</pubDate><guid>https://joshstrange.com/2024/02/03/apple-vision-pro-first-impressions/</guid><description>&lt;p&gt;Let me start by saying the Apple Vision Pro (AVP, sorry Apple, I’m not typing it out each time) is an amazing product but it has some significant limitations. I am still undecided on whether I will keep it or return it as my original main use case (replacement of my MBP monitors) isn’t up to my standards yet but the AVP impressed me in other ways that might make up for that. I have only spent about 6 hours with the AVP so far and as such my thoughts on it are still very raw and subject to change as time goes on. With that out of the way, let’s get started.&lt;/p&gt;&#10;&lt;h2 id="initial-setup"&gt;&lt;strong&gt;Initial Setup&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;Apple’s packaging continues to be top-tier with the AVP. As other reviewers have mentioned, the box is quite big but as always Apple makes the unboxing a fun experience. The AVP comes with the headset, the Solo Knit Band, the Dual Loop Band, USB-C charger, battery, and a polishing cloth (I know, I know, I was super excited to get a prized &lt;a href="https://en.wikipedia.org/wiki/Apple_Polishing_Cloth"&gt;Apple polishing cloth&lt;/a&gt;. It puts all other microfiber cloths to shame /s). I have not used the Dual Loop Band yet as the Solo Knit Band has been adequate so far but I will try it out soon just to make sure I’m not missing anything. That said, the Dual Loop Band is so clearly an afterthought. It’s not janky or anything but putting it next to the Solo Knit Band shows a stark difference in attention to detail. It’s effectively 2 elastic straps while the Solo Knit Band looks like it was engineered to within an inch of its life.&lt;/p&gt;&#10;&lt;p&gt;Setting up the AVP was straightforward if you have an existing iOS device. I was able to get my Wifi, Apple ID, saved cards, and more from my iPhone just by looking at the QR code on it. That said, I wish it had prompted me to move to a more open location. I was just in my kitchen (because I have zero self-restraint and wanted to set it up right away) and the UI was behind my pendant lights which was a little awkward. You do some minor calibration (look at the dots, tap your fingers), scan your QR code if you have prescription lenses, do your typical Apple device setup (restore from backup, add cards to your wallet, etc), and then you are dropped into the main UI grid of icons. Notably absent was the Siri setup. The device has Siri but you don’t need to “train” it like you do on every other Apple product. Siri works while doing something like a Facetime call which I found out by accident. I meant to trigger my phone/watch but it popped up on my AVP and created the reminder for me right there.&lt;/p&gt;&#10;&lt;h2 id="the-future-is-blurry"&gt;&lt;strong&gt;The future is… blurry?&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;With the setup complete I looked around a bit and thought my prescription lenses were wrong. Things were blurrier than I had expected. It’s a massive step up from my Meta Quest 2 (MQ2) but still a bit grainy. The motion blur when you move is disappointing, though understandable, but it’s perfectly manageable. Heck, I walked around my house with my MQ2 in passthrough and that is a B&amp;amp;W grainy mess, this is a significant improvement. I had trouble reading my phone and watch but the UI for the AVP was crisp and the text was very easy to read. There was no squinting or moving my head around to be able to read text like on the MQ2. I took out the prescription lenses and put in my contacts to rule out the possibility of a bad prescription or lenses but the experience was the same.&lt;/p&gt;&#10;&lt;p&gt;While the blurry surroundings were a bit jarring it does fade away once you are using the AVP. Even without using the immersive “Experiences” you get used to it since the AVP UI is always crisp and easy to read. In the same way that when you watch TV you are aware of your surroundings but aren’t focusing on them.&lt;/p&gt;&#10;&lt;p&gt;I won’t lie, I’m disappointed in not being able to read my phone/watch through the AVP. Apple made it seem like that was easy to do and the reviewers who did mention it said they could read their phones/watches. I hope that I’m just doing something wrong (&lt;a href="https://www.engadget.com/2010-06-24-apple-responds-over-iphone-4-reception-issues-youre-holding-th.html"&gt;am I holding it wrong Apple?&lt;/a&gt;) or that Apple finds a way to improve this. Something like AirPlaying the device UI into the AVP so you don’t see the phone itself but an overlaid video feed? I think the phone could handle that but I’m not sure if the watch has enough power (both battery and processor) to do it. I felt the need to lift my AVP when a notification came in so I didn’t miss it. I quickly set up apps on my AVP that would deliver the notifications I needed to see most.&lt;/p&gt;&#10;&lt;h2 id="interacting-with-the-avp"&gt;&lt;strong&gt;Interacting with the AVP&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;The AVP UI itself is beautiful, crystal clear, and easy to navigate. In fact every AVP-native app I used was quite enjoyable. It was the iPad/iPhone apps that had some teething problems. Mainly around UI with too-small tap targets (look targets?). In a few cases with non-AVP-native apps, I had to move my head to better look at a target I wanted to tap. This is where a trackpad or mouse comes in very handy. I paired a trackpad with my AVP and used it to tap on things I couldn’t do easily with my eyes alone. Your cursor will never float in the space between windows, instead it “jumps” to the window you are looking at. I had read reviews about this and was skeptical of how it would feel but I was pleasantly surprised at how natural it is. That brings us to input as a whole.&lt;/p&gt;&#10;&lt;p&gt;Looking where you click is cool but it’s a completely different paradigm from what we are used to. To quote Nilay Patel, “&lt;a href="https://www.theverge.com/24054862/apple-vision-pro-review-vr-ar-headset-features-price"&gt;magic, until it’s not&lt;/a&gt;”. When you first get started it works magically but as you get used to it and start to speed up you run into a few minor issues. My issues centered around my eyes wanting to “move on” from what I was doing and/or looking elsewhere thinking my hand would be able to select/scroll/etc on its own. More than once I clicked the wrong thing because my eyes drifted, like they can on my MBP, while I was typing. It would be fine if I had a keyboard paired with the device, but it doesn’t work if you use your eyes to select letters.&lt;/p&gt;&#10;&lt;p&gt;Another note on scrolling, it gets tedious when you have to do a lot of it. It requires you to pinch your fingers together, and then move your hand up or down. You can pinch-flick to scroll faster but then you can’t read what scrolls past. I felt some fatigue while doing this. At one point I was scrolling through the coupons in the Kroger app (it’s not AVP-native, I just wanted to see how grocery shopping would be in the AVP) and I finally got up and paired my Magic Trackpad to the AVP so I could scroll better. I don’t know if Apple could do something like the 2-finger scroll on a trackpad with finger tracking or if that would have too many false positives.&lt;/p&gt;&#10;&lt;p&gt;The AVP has a floating keyboard that pops up when you are entering text and you can tap on the keys with your fingers, look at the keys, and tap your fingers together to select them, or use voice input. Voice input is probably what you want to reach for when possible, though I think they need to improve that experience, followed by look-tap. Tapping with your fingers is cool for about 5 seconds but gets tiring and isn’t as accurate as look-tap. Maybe Apple can implement a “swipe” keyboard, I don’t use it on iOS but I think it might be pretty cool and useful here.&lt;/p&gt;&#10;&lt;p&gt;Voice input does well, it’s one thing Siri can handle, but I found myself talking too early in most cases. I’d “press” the button, see it show the clicked-state, and start talking only to be interrupted by the tone that it has started listening… in the middle of what I was saying. That’s to be expected but Apple can and should improve this UI. Fill the button out slowly from the center or in a clockwise “wipe” until it’s ready to listen. I don’t like waiting for an audio cue to start talking when the visual one has already completed. They even have a UI like this in the App Store search field where you just look at the microphone, it fills out, and then it’s listening. However, even that UI is slightly buggy. If you don’t start talking right away it will stop listening and it’s a little too quick to stop listening if you pause for even a moment. I’m sure this is a similar issue to making &lt;a href="https://stackoverflow.com/questions/29451162/how-to-accelerate-the-identification-of-a-single-tap-over-a-double-tap"&gt;single tap slower to wait to see if you are going to double tap&lt;/a&gt; and Apple wanting it to search as soon as you finish talking but I found it annoying.&lt;/p&gt;&#10;&lt;h2 id="connecting-to-my-mac"&gt;&lt;strong&gt;Connecting to my Mac&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;This was the #1 use case I had for the AVP. And as I said at the top of this post it’s not there yet. I’m not ready to write this off with the current hardware but playing around with it for 10-15 minutes made me realize that while it’s a massive leap over the MQ2 (and the MQ3/MQP from what I’ve read at least) it’s still not ready to be my daily driver The text is crisp when you are looking at it but the text you aren’t looking at is slightly blurry. Maybe I could get used to it (after all the blurry text IS text I’m NOT looking at) but it was distracting and brought back memories of having to focus intently in the MQ2 to read text. I want to be clear, that was NOT the experience I had with the AVP, the text was always clear when I looked at it but my brain kept saying “The &lt;em&gt;other&lt;/em&gt; text is blurry”. My naive assumption is that this is related to foveated rendering but I could be completely off-base. My understanding is the MBP screen mirroring is using AirPlay under the hood so the MBP is just sending a video feed. The AVP wouldn’t need to “render” the UI, but maybe it saves some battery/CPU by not rendering the feed at full resolution unless you are looking at it.&lt;/p&gt;&#10;&lt;p&gt;I am very intrigued by &lt;a href="https://www.macrumors.com/2024/02/02/apple-selling-vision-pro-developer-strap/"&gt;this device&lt;/a&gt; that went on sale yesterday and what that could mean for virtual displays. I know it’s a developer device but let’s be honest, so is the AVP right now. I know plenty of non-developers bought it but it’s a v1, early-days device, it’s effectively a dev kit plus a little more. I will keep an eye out for any open source or paid projects that make use of this “Developer Strap” to feed more data from my MBP to my AVP and I’d buy it in a heartbeat if someone comes out with a better virtual display solution using it. I’m already going to be plugged in for long stretches, so why not plug into my MBP?&lt;/p&gt;&#10;&lt;p&gt;One last thing I’ll mention about connecting to your Mac is how seamlessly you can use your mouse/keyboard with other Vision Pro apps. This is where Apple shines the brightest, the interoperability in its ecosystem. I assume this is built on top of &lt;a href="https://support.apple.com/en-us/102459"&gt;Universal Control&lt;/a&gt;, which I have never used, and it works perfectly. Just look over at an AVP app (native or not), select a text field, and start typing on your Mac’s keyboard. It “just works” in the most Apple way possible.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://marco.org/"&gt;Marco Arment&lt;/a&gt; mentioned that the AVP was not going to be a monitor replacement on &lt;a href="https://atp.fm/"&gt;ATP&lt;/a&gt; (and I think on &lt;a href="https://daringfireball.net/thetalkshow/"&gt;The Talk Show&lt;/a&gt;) and he was 100% correct. I didn’t want to believe him but his comments about how to think about it more like an Apple TV than a Mac were spot on in my experience. Let’s just hope the app ecosystem blows Apple TV’s out of the water, which isn’t hard to do and has probably already happened.&lt;/p&gt;&#10;&lt;h2 id="personas"&gt;&lt;strong&gt;Personas&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;I’d be remiss if I didn’t mention these. I know you’ve already read multiple articles (aka every AVP review) that talk about this so I’ll be brief. Yes, it falls into “Uncanny Valley” territory, yes it’s weird, yes it looks like I’ve injected Botox into my face, but also, yes, people knew it was me right away. My sisters and my mom started the FaceTime calls I made to them by laughing, rightfully so. I spent over an hour on FaceTime and while I’m sure their experience was different (having to look at Botox Josh) I enjoyed it. Sitting by a lake (using the AVP “Experiences”) while my mom hovered on a massive screen in front of me was cool. Much better than holding up your iPhone while on FaceTime.&lt;/p&gt;&#10;&lt;p&gt;One annoyance for me was that FaceTime does not show you your persona, you only get to see the other people. If you use Zoom then you can see yourself and after trying both I’m still conflicted about which I prefer. In general, I like to see how I’m being presented to other people but it was also distracting when I used Zoom since I didn’t like how I looked or how static my face (especially around my eyes) looked. I’m used to checking how I look regularly in a video call to make sure my face reflects how I feel or what I want to convey and my persona felt very limiting. I’m not sure if I would have rather had a Memoji representing me or not. Persona is still in beta so I’m interested to see how it progresses over time.&lt;/p&gt;&#10;&lt;h2 id="killer-app"&gt;&lt;strong&gt;Killer app&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;People want to know what the killer app is for AVP and I think in general we just don’t know yet. The Apple Watch at launch was a confused product and it took a year or two before Apple leaned into the fitness angle instead of “iPhone on your wrist” which is how I felt it was launched. I was coming from a Pebble watch at the time and so I was already sold on “Notifications on your wrist plus fitness tracking” but I know a lot of people were disappointed in what it could do (or more often what it couldn’t do. That Series 0 was rough). Back to the AVP, I tried a few apps and while some were very cool to try out, none felt like “this is the future”. I need more time with the device (as do developers) but I can feel the potential, we are just waiting on the software. All the AVP-native apps were a clear step up from iPad/iPhone apps on the AVP but it’s still early days.&lt;/p&gt;&#10;&lt;p&gt;Things like &lt;a href="https://apps.apple.com/gb/app/widgetsmith/id1523682319"&gt;Widgetsmith&lt;/a&gt; are poised to provide amazing utility to the AVP in the form of widgets you can place in your surroundings. Little things that don’t need a full app like clocks, timers, weather (though &lt;a href="https://apps.apple.com/us/app/carrot-weather-alerts-radar/id961390574"&gt;Carrot Weather&lt;/a&gt; is great and has an AVP-native app), etc. Honestly, I’m surprised Apple doesn’t natively support iOS widgets in AVP. I don’t think Widgetsmith has timers built in yet but it’s something that should be added since it’s a cool feature. &lt;a href="https://apps.apple.com/us/app/crouton-recipe-manager/id1461650987"&gt;Crouton&lt;/a&gt; allows you to break out timers you can place around your kitchen while you cook which is so freaking cool. I really hope my recipe app, &lt;a href="https://www.paprikaapp.com/"&gt;Paprika&lt;/a&gt;, adds something like this. I haven’t cooked in my AVP yet but I plan on trying that out.&lt;/p&gt;&#10;&lt;p&gt;The app I probably spent the most time in, aside from setting up apps, was Photos. This should honestly be the first thing you try in your AVP. Spatial videos shot on my iPhone were cool but limited (there is only so much you can do when the lenses are that close) but panoramic shots are, quite literally, breathtaking. I was instantly transported back to where I took the picture. Unfortunately, most of those pictures are from 2012 (when the feature was released) since I saw very little utility in taking them and they are sometimes frustratingly difficult to take. Because of that, the quality wasn’t great but I have a few newer ones that look much better. Jumping back to an old office, old apartment, or my partner’s grandma’s house was a massive wave of nostalgia. In the future, I want to get better about taking more pictures like this and I look forward to Apple improving the iPhone’s ability to take spatial videos and photos. I took 2-3 pictures with my AVP and then looked at them later and it felt like I was back where I took them. The 3D effect was amazing and while the resolution is low and the edges fade out it was still so cool to be “transported” back to that moment. I know we all cringed at the dad filming his kids on the AVP but I think that will be more commonplace once people see the results of filming/taking a picture in the AVP. Again, the resolution needs to get better but it’s impressive even now.&lt;/p&gt;&#10;&lt;p&gt;Sidenote, Apple needs to make it easy to take pictures of the UI inside the AVP using the same button for pictures of your surroundings. All I found was screen recording in the control panel, which was oddly confusing. It gave me a list of programs which I thought was a list of which program to record but I think it was a list to decide which program should get the final recording. Just odd and at first I thought screen recording wasn’t working or was limited in some way. Bottom line: I want to be able to take just a picture, not only a video.&lt;/p&gt;&#10;&lt;h2 id="laying-down"&gt;&lt;strong&gt;Laying down&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;I spent the majority of my time in the AVP on my couch but near the end of the night, I was getting tired and wanted to try it in my bed. Window management worked fine while more reclined but that’s also when I finally got around to trying Apple TV’s experiences. Unfortunately, there doesn’t seem to be a way to adjust the “horizon” angle for things like Apple TV’s theater/immersive mode or their immersive content (like the &lt;a href="https://tv.apple.com/us/episode/highlining/umc.cmc.5s419yrml4xuumjywusbyjd2b"&gt;Highlining&lt;/a&gt; special). This means you need to be sitting up straight or standing to really appreciate them. For the Highlining special, I had to sit up more and even then I found some of the things I wanted to look at awkward or annoying. It was impressive (though I rolled my eyes plenty at some of the dialog) but I wasn’t able to fully enjoy it in bed. I might rewatch part of that today sitting up to see how it compares. Likewise, Apple TV’s immersive feature only works if you are sitting up. If you are lying down you have to look down too far to center the screen. If you just use a floating window you can place it anywhere, like at a better angle above you so you can relax while watching.&lt;/p&gt;&#10;&lt;h2 id="passwords"&gt;&lt;strong&gt;Passwords&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;I use 1Password to manage my passwords and its iPad app worked fine in the AVP. However, automatic password insertion does not work well at all. I think it worked once or twice (maybe in system apps?) but every third-party app I used would lock up when you selected the password suggestion above the keyboard. Even clicking the “key” icon would lock up the app. Your only recourse when this happens is to force-quit the app (Hold the Digital Crown and the top button at the same time until a macOS-like force quit dialog opens). At first, I thought this was limited to apps that used a webview but I ran into it with 100% native (iOS/iPadOS-native, not AVP-native) apps as well. You’ll need to copy and paste from your password manager for now. I’m not sure if it’s an Apple problem or a 1Password problem (I didn’t test with Apple’s built-in password manager) but I hope it’s fixed soon as copy/paste/text-selection in the AVP is just as annoying as it is on iOS.&lt;/p&gt;&#10;&lt;h2 id="other-notes"&gt;&lt;strong&gt;Other Notes&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;In no particular order here are a few things I didn’t cover above:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I wish I could make some screens smaller or that the iPad app would switch to the iPhone app once I resized it under a certain threshold.&lt;/li&gt;&#10;&lt;li&gt;Petting my dog sometimes was interpreted as a tap/scroll which was jarring.&lt;/li&gt;&#10;&lt;li&gt;I want to be able to organize my apps and mix the native/non-native apps on the icon grid.&lt;/li&gt;&#10;&lt;li&gt;I want Spaces for AVP. You can long-press the close button to hide other apps but I want to be able to go back to all my apps without having to reopen them. Spaces would be perfect so I could leave my chat apps in a different “space” and easily switch to it when needed.&lt;/li&gt;&#10;&lt;li&gt;You don’t have to press the digital crown to open your app grid, just look up to the Control Center icon, tap your fingers, look at the grid icon, and tap again. I wish I had realized this earlier as I was frustrated with having to reach up the headset every time I wanted to open a new app.&lt;/li&gt;&#10;&lt;li&gt;I didn’t experience any motion sickness with the AVP like I have with MQ2 but I also wasn’t playing any games (aside from trying a 2D game). The Highline special was the closest I came to “moving in VR while my body was stationary” and I didn’t have any issues with it.&lt;/li&gt;&#10;&lt;li&gt;The dinosaur experience is pretty cool and shows off the 3D video feature very well&lt;/li&gt;&#10;&lt;li&gt;In full immersion, if you have a fan on you might see a flickering around your hands as the light changes slightly.&lt;/li&gt;&#10;&lt;li&gt;I was very disappointed that the excellent &lt;a href="https://apps.apple.com/us/app/callsheet-find-cast-crew/id1672356376"&gt;CallSheet&lt;/a&gt; app is not available for AVP. It’s a perfect use case and I have no idea why the developer, &lt;a href="https://www.caseyliss.com/"&gt;Casey Liss&lt;/a&gt; of &lt;a href="https://atp.fm/"&gt;ATP&lt;/a&gt;, disabled his app for AVP. I know he said he was working on an AVP-native version but come on, let us have the iPad app in the meantime. &lt;a href="https://marco.org/"&gt;Marco&lt;/a&gt; allowed &lt;a href="https://overcast.fm/"&gt;Overcast&lt;/a&gt; to work as-is which I’m thankful for.&lt;/li&gt;&#10;&lt;li&gt;I wish I could mark an audio app as being “surround” sound (I know that means something different in the context of audio, it’s the best I could come up with). It’s cool that you hear people on FaceTime or Zoom based on where they are on your screen but I didn’t want sound from my audiobook app, &lt;a href="https://prologue.audio/"&gt;Prologue&lt;/a&gt;, to only come from where I physically (virtually?) placed it in my space.&lt;/li&gt;&#10;&lt;li&gt;I think I’ll return the $200 carrying case, I thought the exterior would be hard (fabric stretched over plastic) but the exterior is like a puffy windbreaker jacket. It looks odd, I’ll buy a much cheaper black case online. This was a case of “what’s another $200 when I’m spending this much already” that I now regret.&lt;/li&gt;&#10;&lt;li&gt;I didn’t notice the weight of the headset at all. As in it didn’t bother me. My MQ2 was heavier and started to bug me after a while of wearing it. I wore the AVP for ~6 hours straight and it didn’t bother me.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="conclusion"&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/h2&gt;&#10;&lt;p&gt;In wrapping up my initial test drive with the Apple Vision Pro, it’s clear this device isn’t just another tech gadget; it’s a bold leap into a future we’ve only seen in sci-fi flicks. Despite not hitting the mark as a MacBook Pro monitor replacement, at least not yet, it has unveiled a realm of possibilities that excite the imagination. From the surreal experience of viewing photos and panoramas, the AVP has thrown the door wide open to what “immersive” truly means. Sure, we’ve hit some bumps along the way (the odd blurriness outside the focal point, the teething issues with app integration, and the occasional input method fatigue) but these feel more like minor problems of a first-gen product than deal-breakers. And while the killer app for the AVP remains to be seen, the potential for game-changing applications is palpable in every interaction. The Apple Vision Pro, with its current quirks and untapped potential, is like the first iPhone, it hints at a revolution. As I decide whether to keep it or not, I’m swayed not just by what the AVP is today, but what it promises for tomorrow and future iterations (it’s “iPhone 4” moment). The journey into mixed reality is just beginning, and if history is any guide, Apple is likely to refine and perfect this vision. In the end, the AVP is more than a piece of technology; it’s a promise of what’s to come, and I, for one, am here for the ride.&lt;/p&gt;&#10;</description></item><item><title>Securing all your self-hosted apps with Single Sign-on</title><link>https://joshstrange.com/2020/01/13/securing-your-self-hosted-apps-with-single-signon/</link><pubDate>Mon, 13 Jan 2020 17:27:32 +0000</pubDate><guid>https://joshstrange.com/2020/01/13/securing-your-self-hosted-apps-with-single-signon/</guid><description>&lt;p&gt;There are multiple approaches to how best secure your self-hosted apps as well as multiple ways to reverse proxy your self-hosted apps but today I will focus on how I use Caddy, http.login plugin, and the http.jwt plugin to secure my apps.&lt;/p&gt;&#10;&lt;p&gt;I have a number of self-hosted apps running on my home network and in the cloud and each app likes to implement it’s own auth. Some support picking between Basic Auth and a login page while some don’t support any authentication at all. For years I’ve relied on the individual pieces of software to manage themselves using their built-in auth (SyncThing, ZoneMinder, Tautulli, etc). For things that didn’t implement auth (DarkStat, NetData, Calibre, etc) I would add a directive to my Caddyfile to implement basic auth.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;my-sub-domain.mydomain.com {&#10; basicauth / user pass&#10; proxy / IPADDRESS:PORT {&#10; transparent&#10; websocket&#10; }&#10; gzip&#10; tls your@email.com&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Overall it seemed like a decent system. I was really bad about reusing passwords between these services because A) I didn’t think I’d be a big target and B) Laziness. Entering a username/password for &lt;code&gt;basicauth&lt;/code&gt; on iOS used to be a PITA and while it has gotten better (now that it will suggest a user/pass for you) I have found I can more reliably/more quickly enter my U/P before I can: Click correct login, wait for FaceID to launch it’s UI, wait for FaceID to scan my face, wait for page to shift back (entire page re-flows when keyboard hides to show FaceID UI), then click the “Login” button.&lt;/p&gt;&#10;&lt;p&gt;Obviously this is a terrible practice and it’s bothered me for coming up on years now. I know better, at a bare minimum I should have been using a wide array of U/P’s but due to iOS and what I’ll called “shared base host name failings in PW managers” I didn’t (A lot of autofill solutions think service1.mydomain.com and service2.mydomain.com must use the same U/P 🙄).&lt;/p&gt;&#10;&lt;p&gt;For at least a couple months now I’ve been kicking around the idea of having a single point of truth for logins. One place I could change my U/P for all my services and ideally a way to login to ServiceA and be able to access ServiceB without logging in again. I had read a number of tutorials/guides/blog posts on doing bits and pieces of what I wanted but I never put it all together. Finally I bit the bullet and to my surprised it was way easier that I had anticipated.&lt;/p&gt;&#10;&lt;p&gt;I use &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt; for my reverse proxy (as of this going live I’m using v1 still but I will switch to v2 once released and the needed plugins are ported). Even though I was intimately familiar with Nginx due to using it for work/other projects I picked Caddy for my personal needs mainly due to it’s stupid-easy TLS support. I am aware that today there exists a number of solutions for auto-generating Let’s Encrypt certs in Nginx but when I picked Caddy those solutions were hacky at best (especially when doing all this in docker). In Caddy I can reverse proxy an internal app in just a few lines of code as seen below:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;my-sub-domain.mydomain.com {&#10; proxy / IPADDRESS:PORT {&#10; transparent&#10; websocket&#10; }&#10; gzip&#10; tls your@email.com&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That’s it, when Caddy starts up it will automatically do the ACME song and dance to get a Let’s Encrypt cert for &lt;code&gt;my-sub-domain.mydomain.com&lt;/code&gt; and auto-renew it without me having to do a thing. In the example above you can see how easily it is to add &lt;code&gt;basicauth&lt;/code&gt; and a lot of Caddy feature are that easy to use which made Caddy an easy choice for me.&lt;/p&gt;&#10;&lt;p&gt;For this blog post I’m just going to implement a basic-auth-like setup but with SSO across all your services. You can easily swap out the username/password pair config to use any of the following authentication methods:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;* Htpasswd&#10;* OSIAM&#10;* Simple (user/password pairs by configuration) &amp;lt;- What we are going to use&#10;* Httpupstream&#10;* OAuth2&#10; * GitHub login&#10; * Google login&#10; * Bitbucket login&#10; * Facebook login&#10; * Gitlab login&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I am going to assume that you, like me, are using a single &lt;code&gt;Caddyfile&lt;/code&gt; for all your domains like me. Here is, in its entirety, what your &lt;code&gt;Caddyfile&lt;/code&gt; will need to contain:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;(secure) {&#10; jwt {&#10; path /&#10; redirect https://auth.mydomain.com/login?backTo=https://{host}{uri}&#10; # More on these `except`&amp;#39;s below&#10; except /abc&#10; except /xyy&#10; }&#10;}&#10;&#10;auth.mydomain.com {&#10; login {&#10; # Here is your U/P but we can replace&#10; # this line with a different auth method&#10; # (like using Google login) if we want&#10; simple myusername=mypassword &#10; jwt_expiry 24h&#10; redirect_check_referer false&#10; # 1 line per host &amp;#34;service1.mydomain.com&amp;#34;,&#10; # this is needed to redirect back after login&#10; redirect_host_file /root/.caddy/hosts &#10; cookie_expiry 2400h&#10; # Important if your services are all on their own subdomains&#10; cookie_domain mydomain.com &#10; }&#10;}&#10;&#10;&#10;service1.mydomain.com {&#10; import secure&#10; proxy / IPADDRESS:PORT {&#10; transparent&#10; websocket&#10; }&#10; gzip&#10; tls your@email.com&#10;}&#10;&#10;service2.mydomain.com {&#10; import secure&#10; proxy / IPADDRESS:PORT {&#10; transparent&#10; websocket&#10; }&#10; gzip&#10; tls your@email.com&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let’s break this down starting with the &lt;code&gt;(secure)&lt;/code&gt; block at the top. If you are unfamiliar with Caddy’s syntax (as I was when I embarked on this journey) then you might not know how to have reusable blocks of code in your config. The &lt;code&gt;(secure)&lt;/code&gt; block can be &lt;code&gt;import secure&lt;/code&gt;‘d into any other domain definition we want so that we don’t have to keep copy/pasting the same code block and we keep that config in one place for easy editing.&lt;/p&gt;&#10;&lt;p&gt;Inside the &lt;code&gt;(secure)&lt;/code&gt; block I am using the &lt;a href="https://caddyserver.com/v1/docs/http.jwt"&gt;http.jwt&lt;/a&gt; (&lt;a href="https://github.com/BTBurke/caddy-jwt/blob/master/README.md"&gt;docs&lt;/a&gt;) plugin for Caddy. Very simply it secures the entire domain &lt;code&gt;path /&lt;/code&gt; and then tells the Caddy where to redirect you if it doesn’t find a valid JWT:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; redirect https://auth.mydomain.com/login?backTo=https://{host}{uri}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I am pointing Caddy over to my &lt;code&gt;auth&lt;/code&gt; domain and telling it to redirect me &lt;code&gt;backTo&lt;/code&gt; the service I came from once I am authenticated.&lt;/p&gt;&#10;&lt;p&gt;Moving on to my &lt;code&gt;auth.mydomain.com&lt;/code&gt; block and most of this should be somewhat self-explanatory. We are using the &lt;a href="https://caddyserver.com/v1/docs/http.login"&gt;http.login&lt;/a&gt; (&lt;a href="https://github.com/tarent/loginsrv"&gt;docs&lt;/a&gt;) plugin using it’s &lt;code&gt;simple&lt;/code&gt; authentication method. Again this is just for the purposes of this blog post, in practice I will probably keep using Google’s OAuth but I want to make this…. simple…. to setup as a POC.&lt;/p&gt;&#10;&lt;p&gt;Then we just need to &lt;code&gt;import secure&lt;/code&gt; in our two services and we are good to go. Really, that’s it. Once you reload Caddy and try to visit &lt;code&gt;service1.mydomain.com&lt;/code&gt; you will get bounced over to &lt;code&gt;auth.mydomain.com&lt;/code&gt; and shown a login screen:&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2020/01/13/securing-your-self-hosted-apps-with-single-signon/screenshot-2020-01-13-11.17.12.png"&gt;&lt;img src="https://joshstrange.com/2020/01/13/securing-your-self-hosted-apps-with-single-signon/screenshot-2020-01-13-11.17.12_hu_4b99922f83c15fc6.webp" width="425" height="309" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;If you take a look at the current URL you will see that will redirect you back to service1 once you login &lt;code&gt;https://auth.mydomain.com/login?backTo=https://service1.mydomain.com&lt;/code&gt;. Now we just need to login with the username and password your provided in your Caddyfile &lt;code&gt;simple myusername=mypassword&lt;/code&gt; and we should be redirected back to service1 with a fresh new &lt;code&gt;jwt_token&lt;/code&gt; cookie.&lt;/p&gt;&#10;&lt;p&gt;Now, depending on the apps you are hosting you might find there are routes that don’t need to be authenticated via this method. Maybe they are public routes or maybe they have their own authentication method (like an API token). This is where the &lt;code&gt;except /abc&lt;/code&gt; comes in. You will need to determine which paths need to be ignored for auth and add them for all your services to the top &lt;code&gt;(secure)&lt;/code&gt; block. I wish there was a way to “extend” the &lt;code&gt;http.jwt&lt;/code&gt; plugin on a per-host basis but I was unable to find a way and I opted for whitelisting for all domains in the &lt;code&gt;(secure)&lt;/code&gt; block rather than copy/pasting the &lt;code&gt;jwt&lt;/code&gt; block into each service I wanted to secure.&lt;/p&gt;&#10;&lt;p&gt;In practice the endpoints you whitelist either won’t be used by your other services or if they are used they are probably protected via an auth method. What I mean by that is if you have to whitelist &lt;code&gt;except /api&lt;/code&gt; then any other service that might use &lt;code&gt;/api&lt;/code&gt; as a path is probably using an API token as well. This is not a 100% across the board rule but I did my research on every service I have and it was the case for me. At worst you can copy/paste the &lt;code&gt;jwt&lt;/code&gt; block into a service if you need to limit the &lt;code&gt;except&lt;/code&gt;‘s. You may end up reverse-proxying some services that don’t need to be secured at all (static file host, Plex, other services that use their own auth that you don’t want to replace) for these I just leave out the &lt;code&gt;import secure&lt;/code&gt; directive.&lt;/p&gt;&#10;&lt;p&gt;And that’s it, just by adding 2 blocks to your Caddyfile and importing the secure block on the sites you want to protect you have SSO for all your services! If I had known it was this easy I would have do it years ago.&lt;/p&gt;&#10;</description></item><item><title>My Mac Apps</title><link>https://joshstrange.com/2019/09/26/my-mac-apps/</link><pubDate>Thu, 26 Sep 2019 19:31:10 +0000</pubDate><guid>https://joshstrange.com/2019/09/26/my-mac-apps/</guid><description>&lt;p&gt;I have a friend who is setting up a new Macbook for work and while this is not his first Mac it’s the first he will be using as a daily driver in some time. I also am setting up a new Macbook I got from work and I have been sending my friend the apps I like and use. During this process I realized it this is far from the first time I’ve done this. Therefore, here is my list of my mac apps. Some are free, some are paid, some are for work, some are for fun, and some might be window dressing I paid for that can be done for free on the command line 😉.&lt;/p&gt;&#10;&lt;p&gt;I would like to specify that none of these apps/services had any prior knowledge they would be on this list and I am not being compensated in ANY way for their inclusion. The ONLY possible exception to this is my Backblaze &lt;a href="https://secure.backblaze.com/r_invite.htm?email=&amp;amp;bznetid=7172457511569513652770"&gt;referral link&lt;/a&gt; and I added it below the main link and specifically called out that it is a referral link. I recommend Backblaze because I’ve used them practically since launch to secure 5+ computers over my time with them and I think highly of them as a company. (Also their &lt;a href="https://www.backblaze.com/blog/hard-drive-stats-q2-2019/"&gt;hard drive reliability stats&lt;/a&gt;… 😍🤤)&lt;/p&gt;&#10;&lt;h2 id="1password--paid-3mo-individual-5mo-family"&gt;&lt;a href="https://1password.com"&gt;1Password&lt;/a&gt; – Paid ($3/mo individual, $5/mo family)&lt;/h2&gt;&#10;&lt;p&gt;This is my favorite password manager that I’ve ever tried. I used Dropbox in the past for syncing but now I let 1Password sync my passwords and just pay monthly. If you aren’t using some kind of password manager I urge you to try out 1Password, it had great integration with all major browsers and mobile platforms. I have no idea what 99% of my passwords even are at this point, they are all random jumbles of letters, numbers, symbols that 1Password manages for me. 1Password is also my repository for my drivers license, passport, SS numbers of family, software licenses, OTP’s, etc.&lt;/p&gt;&#10;&lt;h2 id="alfred--paid-28"&gt;&lt;a href="https://www.alfredapp.com/"&gt;Alfred&lt;/a&gt; – Paid (~$28)&lt;/h2&gt;&#10;&lt;p&gt;The Mac OS spotlight bar has come a long way but I still can’t give up Alfred. I really only scratch the surface of what it can do but I use it for app launching, clipboard memory (LIFE SAVER), a few workflows, keyboard shortcuts (I know Mac OS has this now but this is better), and more I’m sure I’m forgetting.&lt;/p&gt;&#10;&lt;h2 id="backblaze--paid-6mo"&gt;&lt;a href="https://www.backblaze.com/"&gt;Backblaze&lt;/a&gt; – Paid ($6/mo)&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://secure.backblaze.com/r/00p8xd"&gt;(referral link, I get a free month and you get a free month but I think they give that to everyone)&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Unlimited backup for your mac for just $6/mo. I gives me such peace of mind to have backups of what I do. This is not the only backup tool on this list but I won’t go without it. They will even send you a hard drive or USB drive in the mail with your files for free (after you return it of course). You have 30 days of file history which is a nice fallback and they even have mobile apps to view your files. The mobile apps kind of suck and I rarely use them but it’s nice to be able to snag any file off your laptop even if it’s close and not online.&lt;/p&gt;&#10;&lt;h2 id="bartender-3--paid-15-4-week-trial"&gt;&lt;a href="https://www.macbartender.com/"&gt;Bartender 3&lt;/a&gt; – Paid ($15, 4-week trial)&lt;/h2&gt;&#10;&lt;p&gt;If you are like me you have too many icons in your menu bar. On a big monitor that’s not always an issue but on a Macbook 13″ or 15″ it can get bad. Bartender will let you hide menu bar apps that you don’t need to see but still give you quick access to them. With just a click you can toggle between your hidden and shown menu bar items. You can even have bartender hide them unless they have updated. For example: my Dropbox menu item is hidden UNLESS it is animating to tell me it’s syncing/updating and then Bartender will show it in the menu bar where I can react accordingly. This lets me clear my menu bar of everything I need rarely or not at all (I dislike apps that give no option to hide the menu bar icon, looking at you Sonicwall 😠).&lt;/p&gt;&#10;&lt;h2 id="base-2--paid-25-limited-trial"&gt;&lt;a href="https://menial.co.uk/base/"&gt;Base 2&lt;/a&gt; – Paid (~$25, limited trial)&lt;/h2&gt;&#10;&lt;p&gt;I have a hard time suggesting this one to most people unless you work with sqlite databases and just don’t like the other options out there. I’ll still include it for now since I do use it. It’s a pretty sqlite client, that’s about it, it’s probably not worth $25 to be honest. If you use IDEA you can manage sqlite DB’s from within it and that’s probably what I will do going forward.&lt;/p&gt;&#10;&lt;h2 id="bettertouchtool--paid-750-21-lifetime-45-day-trial"&gt;&lt;a href="https://folivora.ai/"&gt;BetterTouchTool&lt;/a&gt; – Paid ($7.50, $21 lifetime, 45 day trial)&lt;/h2&gt;&#10;&lt;p&gt;This is a great tool that I use probably 1% of. I can’t even start to get into everything it does but it can handle keyboard shortcuts, keyboard macros, gestures, touch bar modifications, and the list goes on. What do I use it for? Custom touch bars. I really should try out it’s other features but I wanted some touch bar buttons in a game and this was the best way to handle it.&lt;/p&gt;&#10;&lt;h2 id="bitbar--free"&gt;&lt;a href="https://getbitbar.com/"&gt;BitBar&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;I’ll let the repo say it best “Put the output from any script or program in your Mac OS X Menu Bar”. What does this mean? Well you can write scripts (in any language that can run on the mac) to output data that will display in your menu bar. Just go look at some of the example plugins. I’ve written my own in JS/TS to monitor various things and they are very easy to create.&lt;/p&gt;&#10;&lt;h2 id="caffeine--free"&gt;&lt;a href="http://lightheadsw.com/caffeine/"&gt;Caffeine&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;Great app, does 1 simple thing very well. It lets me easily keep my monitors awake with a quick toggle in the menu bar. I don’t have a lot to say about this app but it’s a must-install for me.&lt;/p&gt;&#10;&lt;h2 id="carbon-copy-cloner--paid-40-30-day-free-trial"&gt;&lt;a href="https://bombich.com"&gt;Carbon Copy Cloner&lt;/a&gt; – Paid ($40, 30 day free trial)&lt;/h2&gt;&#10;&lt;p&gt;Above I mentioned Backblaze for backups but they are only 1 piece of the puzzle. It’s often said you should have a 3-2-1 backup strategy: 3 copies of the data, 2 different mediums (devices/types), and at least 1 offsite. Backblaze handles my offsite and CCC handles my onsite. CCC is simply amazing, if today I dropped my laptop in front of a bus and it got demolished I would “simply” have to get a new Macbook and plug in my external SSD that CCC has been backing up my Macbook to. From there I can BOOT directly into my old computer and either clone it to the new Macbook or just use it to grab the files I need. Let me repeat that a different way: This is NOT just a backup of your files, it’s a FULL BOOTABLE clone of your internal hard drive. I cannot tell you how much peace of mind this gives me. You can also use CCC to backup to a network drive which I need to setup soon! For those counting at home this just 2 backup methods, my third backup is a Time Machine backup.&lt;/p&gt;&#10;&lt;h2 id="chicken--free"&gt;&lt;a href="https://sourceforge.net/projects/chicken/"&gt;Chicken&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;Formally Chicken of the VNC, this is my go-to VNC client, it’s simple and it works. It’s not the prettiest but it will do. Thankfully I rarely have to VNC and when I do Chicken is far from the ugliest thing I have to look at.&lt;/p&gt;&#10;&lt;h2 id="coderunner--paid-15"&gt;&lt;a href="https://coderunnerapp.com/"&gt;Coderunner&lt;/a&gt; – Paid ($15)&lt;/h2&gt;&#10;&lt;p&gt;This is a tool I would have made fun of in the past and potentially I will again (IDEA’s features that are like this are really good and I can use my keyboard shortcuts). That said, I still install this on my Macs. It lets you run code snippets from a wide array of languages with just a single click. I use it at least once or twice a week to do a small POC of something away from my codebase. Java, PHP, Python, and JS are just a few of the languages it supports and it’s great for grabbing some code off StackOverflow, pasting it in, and running some test data through it to play with how it works.&lt;/p&gt;&#10;&lt;h2 id="daisydisk--paid-10"&gt;&lt;a href="https://daisydiskapp.com/"&gt;DaisyDisk&lt;/a&gt; – Paid ($10)&lt;/h2&gt;&#10;&lt;p&gt;Mac disk space analyser. Yes, I am aware of &lt;code&gt;df -h&lt;/code&gt; AND &lt;code&gt;ncdu&lt;/code&gt; but this tool works great, lets me see where I need to prune, and looks great doing it. I’ve used others, I’ve used the command line, this is my favorite by far.&lt;/p&gt;&#10;&lt;h2 id="dash--paid-30"&gt;&lt;a href="https://kapeli.com/dash"&gt;Dash&lt;/a&gt; – Paid ($30)&lt;/h2&gt;&#10;&lt;p&gt;Documentation viewer++. This thing can offline documentation for you, it can manage code snippets, it can save archives of StackOverflow, and make it all searchable without internet. Now let’s be honest, we are rarely without internet but this is still a great tool even for when you are connected. It’s got official documentation for every major language, docs for popular libraries, user-contributed docs, and if all else fails there is a way to import docs you’ve written or converted.&lt;/p&gt;&#10;&lt;h2 id="dropzone--paid-10"&gt;&lt;a href="https://aptonic.com/"&gt;Dropzone&lt;/a&gt; – Paid ($10)&lt;/h2&gt;&#10;&lt;p&gt;Dropzone is another app that I barely scratch the surface of. I use it primarily to ferry files (SFTP) out to specific locations on remote servers (Think: dropping Linux ISO torrents into a watch folder) but it can do so much more. You can configure it to drop DMG’s on one of it’s icons to automatically open the DMG, copy the app to the Application folder, then put the DMG in the trash. All of the plugins (AFAICT) are written in ruby and so it was easily to take one that did 90% of what I needed and modify it to do that extra 10%. I really should look more into things I can do with this app.&lt;/p&gt;&#10;&lt;h2 id="hyperdock--paid-10"&gt;&lt;a href="https://bahoom.com/hyperdock"&gt;Hyperdock&lt;/a&gt; – Paid ($10)&lt;/h2&gt;&#10;&lt;p&gt;I started using this eons ago after coming to the Mac from Windows 7. I liked the window previews and wanted to replicate it. I still use the windows preview but the most-used feature of Hyperdock for me is the ability to scroll up on the title bar for any application to maximize it to fill the screen. I use that feature alone at least 15 times a day.&lt;/p&gt;&#10;&lt;h2 id="istat-menus-6--paid-12"&gt;&lt;a href="https://bjango.com/mac/istatmenus/"&gt;iStat Menus 6&lt;/a&gt; – Paid ($12)&lt;/h2&gt;&#10;&lt;p&gt;See the weather, system stats, etc in your menu bar. I like to be able to see my CPU, GPU, and Memory in my menu bar and while BitBar is great I like this visualization of this along with the extended data it gives me when I click on it.&lt;/p&gt;&#10;&lt;h2 id="iterm-2--free"&gt;&lt;a href="https://www.iterm2.com/"&gt;iTerm 2&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;Hands down the best terminal for Mac. I have mine hotkeyed so &lt;code&gt;Ctrl+`&lt;/code&gt; toggles it’s visor mode for quick hide/show. It’s integration with tmux as well which is super nice for picking up right where you left off on a server using the power of tmux but being able to leverage the ease of adding/resizing panes/tabs.&lt;/p&gt;&#10;&lt;h2 id="jetbrains-toolkit--paid-89-150yr"&gt;&lt;a href="https://www.jetbrains.com/"&gt;JetBrains Toolkit&lt;/a&gt; – Paid ($89-$150/yr)&lt;/h2&gt;&#10;&lt;p&gt;This is kind of a catch-all for all the IDEA products (Ultimate, PHPStorm, WebStorm, etc). I use this to keep my IDEA Ultimate updated. If you use more than 1 language you should splurge on ultimate. I use it for JS, TS, Python, PHP, Java, Ruby, and more. It’s worth EVERY PENNY and the price goes down each year you use it. I know a lot of people are against subscription services especially for something like this but I would pay double without blinking. Don’t @ me about Atom/Sublime/VS Code/Eclipse/emacs/vim, I’ve tried them and I promise you if you commit to IDEA Ultimate you will find it way more powerful.&lt;/p&gt;&#10;&lt;h2 id="nvalt--free"&gt;&lt;a href="https://brettterpstra.com/projects/nvalt/"&gt;NVAlt&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;NVAlt was the app I never knew I needed but is invaluable to me. On its surface it’s a simple note taking app without WYSIWYG or Rich Text UI. You can still bold things and what not but for me it’s my scratch pad, it’s my quick notes, it’s my copy/paste holding zone. I have this hotkeyed ( &lt;code&gt;Ctrl+1&lt;/code&gt;) and I must hit this tens if not hundreds of times a day to quickly show/hide my notes. I use Simplenote as the syncing backend and I use the Simplenote app on my phone. I use it to compose long chats/iMessages/emails, keep track of a todos, list of the apps for this blog post, list of gift ideas for my partner and their family, and more. Pretty much it’s my brain-dump. Yes I have heard of Apple Notes, yes I have heard of Evernote, NVAlt shines in it’s lightweight no-nonsense approach. I need a few things in my notes app: Lightweight, hotkeyable, full text search, and iOS apps or sync to iOS apps. NVAlt comes through in flying colors.&lt;/p&gt;&#10;&lt;h2 id="phoenix--free"&gt;&lt;a href="https://github.com/kasper/phoenix"&gt;Phoenix&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;Phoenix is a lot more technical than most the apps on this list but it is the perfect tool for me. Once again I probably use only a sliver of the functionality but as someone who used to move between multiple multi-monitor setups it is a god send. Do you ever hate the resizing/moving/etc you have to do when you first go from Laptop to Laptop+Monitors? So do I! I tired &lt;a href="https://cordlessdog.com/stay/"&gt;Stay&lt;/a&gt; for a long time but it either stopped working reliably or my multiple multi-monitor setup was too much for it. When Phoenix I have &lt;a href="https://gist.github.com/joshstrange/ae60bc02d6103816d8f1e207d474c98d"&gt;programmed a hotkey to run some Typescript&lt;/a&gt; (compiled down to JS) that will detect the attached monitors by UUID and then automatically arrange my programs across the monitors like I want. This way when I plug into my setup at home or work everything is resized to fit those specific monitors. I looked long and hard for this solution and tried multiple other offerings. I had to get my hands “dirty” for this but it was well worth it.&lt;/p&gt;&#10;&lt;h2 id="postman--freepaid-8mo"&gt;&lt;a href="https://www.getpostman.com/"&gt;Postman&lt;/a&gt; – Free/Paid ($8/mo)&lt;/h2&gt;&#10;&lt;p&gt;I have been using Postman practically since it was released and quite like it. That said I would be equally open to any of the other apps out there that did the same thing. It works very well as a tool for testing API endpoints and if you aren’t using a tool like this (even for manual one-off testing) you really should look at it or one of its competitors like &lt;a href="https://paw.cloud/"&gt;Paw&lt;/a&gt; (native) or &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt; (electron like Postman).&lt;/p&gt;&#10;&lt;h2 id="rocket--freepaid-5"&gt;&lt;a href="https://matthewpalmer.net/rocket/"&gt;Rocket&lt;/a&gt; – Free/Paid ($5)&lt;/h2&gt;&#10;&lt;p&gt;If you use Slack then you are familiar with the “:emojiname” syntax for searching for emojis or other images your organization has uploaded. Rocket makes that system-wide AND allows you to alias your emojis AND allows you to add custom gifs AND has a huge pack of gifs you can download when you buy it. Never again get annoyed that the 🙌 emoji is called “raised hands” instead of “praise hands”, just add an alias. Think that 😒 emoji should be called “side-eye” instead of “unamused face”? No problem. My biggest complaint with Rocket? I simply don’t have enough time in the day to create gifs of all of the Letterkenny scenes I want to be able to access with just a few keystrokes.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2019/09/26/my-mac-apps/can-confirm-letterkenny.gif"&gt;&lt;img src="https://joshstrange.com/2019/09/26/my-mac-apps/can-confirm-letterkenny.gif" width="480" height="270" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;h2 id="sequelpro--free"&gt;&lt;a href="https://www.sequelpro.com/"&gt;SequelPro&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;The name may imply otherwise but I assure you this app is free. It is hands down the best lightweight MySQL client out there. Full stop. Yes, other tools like &lt;a href="https://tableplus.com/"&gt;TablePlus&lt;/a&gt; have their place but I couldn’t shake SequelPro. I was able to constantly beat people to information using this tool (they used other MySQL GUIs or the CLI) and even though it’s had a few more crashes as of the last few years I still can’t recommended this tool enough. If you don’t like it I’ll refund you personally 😉.&lt;/p&gt;&#10;&lt;h2 id="transmit-5--paid-45"&gt;&lt;a href="https://panic.com/transmit/"&gt;Transmit 5&lt;/a&gt; – Paid ($45)&lt;/h2&gt;&#10;&lt;p&gt;Just about anything Panic puts out I am 100% behind. I cut my teeth on Coda (after finally graduating from NP++) at the start of my web development career and while you will have to pry IDEA from my cold dead hands Coda still has a special place in my heart. But enough about Coda, let’s talk about Transmit. Yes it’s not cheap but it far more than Filezilla with a coat of paint. It supports just about every cloud server under the sun (Dropbox, OneDrive, B2, S3, Google Drive, and more) while still supporting all the protocols you are used to (FTP, SFTP, FTP w/SSL). It does all of this in a BEAUTIFUL package and supports things like syncing folders, batch rename, tags, terminal support, and more. This is truly the swiss army knife of cloud/remote storage.&lt;/p&gt;&#10;&lt;h2 id="the-unarchiver--free"&gt;&lt;a href="https://theunarchiver.com/"&gt;The Unarchiver&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;This is an app I rarely see but when I do I’m very thankful for it. The Unarchiver does was it says on the tin… it unarchives… anything. Period. That’s it. Yes the Mac can handle an array of formats but this app makes it so you never have to worry again about opening an archive.&lt;/p&gt;&#10;&lt;h2 id="vlc--free"&gt;&lt;a href="https://www.videolan.org/vlc/index.html"&gt;VLC&lt;/a&gt; – Free&lt;/h2&gt;&#10;&lt;p&gt;Similar to The Unarchiver handling any archive format, VLC can handle any video/audio format. Quicklook/Quicktime is fine but there are a whole array of formats they just throw up their hands at that VLC will play flawlessly. It’s not the prettiest UI to look at but when you are watching a video or listening to sound you shouldn’t even be seeing the UI so it’s not a problem.&lt;/p&gt;&#10;&lt;p&gt;Well that concludes my list of apps I use. I hope you found something useful or new!&lt;/p&gt;&#10;</description></item><item><title>Lexington Burger Week 2019 App</title><link>https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/</link><pubDate>Sun, 07 Jul 2019 00:48:59 +0000</pubDate><guid>https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/</guid><description>&lt;p&gt;&lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/simulator-screen-shot-iphone-xs-max-2019-07-05-at-12.25.53_o-1.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/simulator-screen-shot-iphone-xs-max-2019-07-05-at-12.25.53_o-1_hu_f2ec55ede2328925.webp" width="1242" height="2688" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;I have been a long time fan of &lt;a href="https://www.lexingtonburgerweek.com/"&gt;Lexington Burger Week&lt;/a&gt;… and &lt;a href="https://www.lexingtonpizzaweek.net/"&gt;Pizza week&lt;/a&gt;, and &lt;a href="https://www.cravelexington.com/"&gt;Crave&lt;/a&gt;, and &lt;a href="https://www.cravetacoweek.com/"&gt;Taco Week&lt;/a&gt;. I’m also a fan of &lt;a href="http://lexingtoncalzoneweek.com/"&gt;satirical food weeks&lt;/a&gt;. Ok I am just a long time fan of FOOD (and &lt;a href="https://www.newcirclecircular.com/"&gt;satire&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;Burger Week is a Big Deal**™** where I work and so for the last few years I’ve painstakingly extracted the list of burgers, put them into Google Sheets and then created &lt;a href="https://www.easymapmaker.com/map/lexingtonburgerweek2018"&gt;maps&lt;/a&gt; (Sorry, I can’t find the 2017 version I made) to make it easier for us to plan out which burgers we wanted to eat and record our experiences.&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery"&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.41.19.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.41.19_hu_a90c4b1d76cb7299.webp" width="900" height="420" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.41.33.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.41.33_hu_70d2a542c1af6217.webp" width="900" height="479" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Some years I’ve had a few more burgers than my doctor would probably recommend…&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery"&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.59.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.59_hu_270830e25f314a9c.webp" width="502" height="163" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.35.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.35_hu_98d968380b86ebda.webp" width="506" height="162" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.47.04.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.47.04_hu_b7d73d53b499db24.webp" width="501" height="161" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.53.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.53_hu_bee2999073d18044.webp" width="501" height="162" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.47.10.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.47.10_hu_6a43fd4b20ad3f51.webp" width="502" height="159" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.48.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.48_hu_6887a995504483d8.webp" width="502" height="161" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.42.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-10.46.42_hu_61f0f6ff1d011799.webp" width="504" height="165" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Bottom line, I quite enjoy #LexingtonBurgerWeek.&lt;/p&gt;&#10;&lt;p&gt;For years, I’ve thought there should be an app for Burger Week and joked with my colleagues that I should just make one myself. As you may have guessed by the title of this blog post (or the image at the top, or by the fact you actually came here from the app)… this year I did it! 🎉&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/simulator-screen-shot-iphone-xs-max-2019-07-05-at-12.26.13.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/simulator-screen-shot-iphone-xs-max-2019-07-05-at-12.26.13_hu_afa6dffd4a07aa60.webp" width="1242" height="2688" alt="The Burgers 😋" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;The Burgers 😋&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You might assume that I’ve been working on this for a while, that I approached the good people over at &lt;a href="https://smileypete.com/"&gt;Smiley Pete&lt;/a&gt; months ago, that I spaced out the development over a sane period of time… You would be wrong. Instead I procrastinated, wouldn’t commit, and generally was lazy until they released the list of burgers for 2019 (June 26th). The next morning I woke up early and got started on extracting all the burger information.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.02.22.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.02.22_hu_4f20387777082a1.webp" width="1600" height="1348" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;It’s amazing what a hard deadline will do for productivity. I had early versions of the app up and running within 24 hours of the list being posted and was asking for help testing the app the day after that. I pushed my first version out to my friends (beta testers) on June 30th and immediately got a TON of extremely valuable feedback.&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery"&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.05.22.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.05.22_hu_f7b0bcd1d92d1803.webp" width="501" height="260" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.08.24.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.08.24_hu_8a9310e1ca82755d.webp" width="503" height="471" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.08.38.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.08.38_hu_5e100b4a356038a3.webp" width="504" height="171" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Honestly this app would &lt;strong&gt;not have been possible&lt;/strong&gt; without the help of everyone who volunteered their time to test out the app. I got amazing feedback, suggestions, and bug reports. I would say I couldn’t have paid for better help but I know some absolutely amazing QA people where I work so I know that’s not strictly true but the sentiment remains. I have said it a number of times but I cannot say it enough: THANK YOU! If you helped test out the early versions of the app I could not have done this without your help. From the people who only opened the app 1 time, had it crash (and send me an automated report) to the people who tore the app apart looking for any and every rough edge to the people who actively (and succeeded) in trying to break it: THANK YOU.&lt;/p&gt;&#10;&lt;p&gt;Up until now I’ve only mentioned &lt;a href="https://smileypete.com/"&gt;Smiley Pete&lt;/a&gt; (the company behind all these wonderful food events and more) in passing. I didn’t leave out any details, it wasn’t until this last Tuesday (July 2nd) that I finally reached out to them through a friend. There are a number of reason I waited so long, first and foremost I wanted something relatively polished before I talked to them. The other big reason was I had no idea how they would react. Would they be happy? Would they send me a cease and desist or DMCA letter? People can be weird sometimes so I was VERY apprehensive going into my meeting with them on Wednesday (July 3rd).&lt;/p&gt;&#10;&lt;p&gt;They were understandably wary at first, I mean they didn’t know me from Adam and so it’s not surprising they wanted to know what I wanted from them or what I expected in return. That answer was easy: Nothing. While I’m not in the habit of working for free (with the exception of &lt;a href="https://github.com/joshstrange"&gt;open source&lt;/a&gt;) I created this app with zero expectation of compensation. It was, as corny as it may sound, a labor of love. Well.. equal parts that and scratching my own itch if I’m being honest.&lt;/p&gt;&#10;&lt;p&gt;Thankfully all my worrying had been for naught. After the initial feeling-out they were very excited about the possibility of having a “Lexington Burger Week App”. They had a million (great) ideas for things they could do with an app and we had to pair down that list to a few basic changes that could be ready in time for this Burger Week. Listening to people play the “what if we…” game is actually a ton of fun for me, I love empowering ideas with technology and seeing people get excited at the possibilities is infectious.&lt;/p&gt;&#10;&lt;p&gt;Over the next 2 days I worked furiously on the changes they wanted pushing out testing build after testing build. My beta testers were the real MVP’s here catching issues and giving feedback all along the way. I would like to apologize to the staff and designer at &lt;a href="https://smileypete.com/"&gt;Smiley Pete&lt;/a&gt;, it was never my intention to throw extra work on your guys plate on a holiday 😬.&lt;/p&gt;&#10;&lt;figure&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.36.13.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.36.13_hu_e07f4d2a5ec61598.webp" width="557" height="721" alt="Notice the release cadence quickening a bit over the last 2 days 🤣" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;figcaption&gt;Notice the release cadence quickening a bit over the last 2 days 🤣&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;It was not all smooth sailing, I lost power July 3rd so had to cut my night short. I also had a lot of &lt;em&gt;fun&lt;/em&gt; on July 4th when Apple’s portal for managing/releasing apps decided to go down… Well that and about half of their cloud services. 🙄&lt;/p&gt;&#10;&#10;&lt;figure class="gallery-wrap"&gt;&#10; &lt;div class="gallery"&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-04-13.56.13.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-04-13.56.13_hu_91e8d96590c7e1c5.webp" width="900" height="544" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-04-13.55.50.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-04-13.55.50_hu_e0f6cc8f2a808672.webp" width="900" height="539" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.42.56.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.42.56_hu_29dcf38b37b5179.webp" width="590" height="205" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.43.02.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/screenshot-2019-07-06-11.43.02_hu_fb197aa6d3e73db4.webp" width="590" height="648" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&#10; &lt;/div&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I was sweating bullets that after all the hard work that I, the beta testers, and &lt;a href="https://smileypete.com/"&gt;Smiley Pete&lt;/a&gt; staff had put in would be too little, too late and that we wouldn’t be able to get the app released in time for burger week. 😥&lt;/p&gt;&#10;&lt;p&gt;I am proud to announce that we did make it! (Even if it was just by the skin of our teeth). Thanks for listening to my rambling. Go get the App and go eat some burgers!&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/download_on_the_app_store_badge_us-uk_rgb_blk_092917.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/download_on_the_app_store_badge_us-uk_rgb_blk_092917_hu_c1ac68764d73406d.webp" width="748" height="250" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/en_badge_web_generic.png"&gt;&lt;img src="https://joshstrange.com/2019/07/07/lexington-burger-week-2019-app/en_badge_web_generic_hu_8fa86ec50eb8314e.webp" width="646" height="250" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Setting up Fastlane with Ionic/Cordova apps</title><link>https://joshstrange.com/2017/10/14/setting-up-fastlane-with-ionic-cordova-apps/</link><pubDate>Sat, 14 Oct 2017 22:54:42 +0000</pubDate><guid>https://joshstrange.com/2017/10/14/setting-up-fastlane-with-ionic-cordova-apps/</guid><description>&lt;p&gt;Recently I was tasked with setting up a build server to build our iOS apps automatically on git push to a certain branch. While the iOS part of this took the majority of my time (Provisioning Profiles, Certs, XCode project files, etc) this will show you how to setup both iOS and Android. I’m writing this because I couldn’t find any guides that worked for Ionic 2/3 and XCode 9. I won’t promise this is the most efficient way (the double iOS building irks me but I haven’t found a away around it yet) or most secure (I’m storing some passwords and certs in the repo and plan to move them out later) but it does work. The steps are essentially the following:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Install &lt;a href="https://docs.fastlane.tools/"&gt;Fastlane&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Setup &lt;a href="https://docs.fastlane.tools/actions/match/"&gt;Match&lt;/a&gt; (used to manage provisioning profiles and certificates)&lt;/li&gt;&#10;&lt;li&gt;Get credentials/keys for &lt;a href="https://docs.fastlane.tools/actions/supply/#setup"&gt;Google Play&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Add in hook to “Upgrade” our XCode project file&lt;/li&gt;&#10;&lt;li&gt;Add &lt;code&gt;platforms/platforms.json&lt;/code&gt; to repo&lt;/li&gt;&#10;&lt;li&gt;Setup your Fastfile (think of it like a makefile/gulpfile/etc)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Some assumptions I’m making:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;You have XCode CLI tools installed &lt;code&gt;xcode-select --install&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;You have &lt;a href="https://brew.sh/"&gt;brew&lt;/a&gt; installed&lt;/li&gt;&#10;&lt;li&gt;You have done this the “hard way” before (You have your apps setup in iTunesConnect/Google Play and have uploaded to Testflight and/or Google Play Alpha in the past)&lt;/li&gt;&#10;&lt;li&gt;You are managing your push notification certs (APNS) yourself, if you want to automate that part look into &lt;a href="https://docs.fastlane.tools/actions/pem/"&gt;Pem&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="install-fastlane"&gt;Install Fastlane&lt;/h2&gt;&#10;&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; install Fastlane via Bundler but I’m personally not super comfortable with Ruby and I’m not good at debugging issues with it. It’s better (IMHO) to use the brew cask version which comes with all the correct versions of everything. If you are reading this guide after attempting to setup Fastlane on your own and installed it via Bundler I suggest you uninstall that version, I ran into multiple issues with it.&lt;/p&gt;&#10;&lt;p&gt;With that out of the way let’s install Fastlane&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew cask install fastlane&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="setup-match"&gt;Setup Match&lt;/h2&gt;&#10;&lt;p&gt;I would suggest you create a new iTunesConnect “Admin” user to let Match run under. It needs to have the “Admin” role to do everything it does and you probably don’t want your build server to have a team members credentials on it. You also need to create a new git repo to hold the Provisioning Profiles and Signing Certificates (PP/SC). I’m using a gitlab repo on our self-hosted instance but you can use any &lt;em&gt;private&lt;/em&gt; git repo.&lt;/p&gt;&#10;&lt;p&gt;Also I am only using Match to manage distribution PP/SC, you can use it for development PP/SC but I personally don’t (I just use XCode automatic signing or that).&lt;/p&gt;&#10;&lt;p&gt;Let’s start with initializing Match:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastlane match init&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you have already created PP/SC in the &lt;a href="https://developer.apple.com/account/ios/profile/"&gt;iOS Dev Center&lt;/a&gt; then we need to delete them. DO NOT WORRY, this will not delete your apps or mess up already uploaded iOS builds (released or otherwise). I know it sounds scary but we are going to start off by running:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastlane match nuke distribution&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will clear out your PP/SC you have for distribution, don’t worry, we are going to create new ones right now:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastlane match distribution&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Okay, we are good to move on setting up our Android certs (you can skip if you only want iOS&lt;/p&gt;&#10;&lt;h2 id="get-credentialskeys-for-google-play"&gt;Get credentials/keys for Google Play&lt;/h2&gt;&#10;&lt;p&gt;Things we need for this step:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Keystore for signing your Android app (myKeystore.jks)&lt;/li&gt;&#10;&lt;li&gt;Config file containing for signing (release-signing.properties)&lt;/li&gt;&#10;&lt;li&gt;Play Credentials (play-credentials.json)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="keystore"&gt;Keystore&lt;/h3&gt;&#10;&lt;p&gt;This should be self-explanatory, just put this in the root of your app repo.&lt;/p&gt;&#10;&lt;h3 id="config-file"&gt;Config File&lt;/h3&gt;&#10;&lt;p&gt;Also put this in the root of your app repo. It should look like this:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;storeFile=myKeystore.jks&#10;storePassword=XXXyourPasswordHereXXX&#10;keyAlias=XXXyourkeyAliasHereXXX&#10;keyPassword=XXXkeyPasswordHereXXX&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="play-credentials"&gt;Play Credentials&lt;/h3&gt;&#10;&lt;p&gt;Follow the &lt;a href="https://docs.fastlane.tools/actions/supply/#setup"&gt;instructions here&lt;/a&gt; and put the resulting JSON file in the root of your app repo.&lt;/p&gt;&#10;&lt;h2 id="add-cordova-hook"&gt;Add Cordova hook&lt;/h2&gt;&#10;&lt;p&gt;We need to modify our &lt;code&gt;platforms/ios/My App.xcodeproj/project.pbxproj&lt;/code&gt; but we don’t want to have to do it by hand (or by opening XCode) because that would defeat the purpose of a build server and we also don’t want to commit this file to git. What this boils down to one of the Fastlane actions we want to use blows up with a fresh &lt;code&gt;ionic cordova prepare&lt;/code&gt; because it thinks the XCode project files is too old. Opening the project in XCode automatically upgrades the file but we need to do it all from the command line. If &lt;a href="https://github.com/apache/cordova-ios/pull/343"&gt;this pull request&lt;/a&gt; has been merged into cordova-ios then this step may no longer be necessary.&lt;/p&gt;&#10;&lt;p&gt;The hook we are going to use can be found here:&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://gist.github.com/joshstrange/cfb4128f1be7ac60c22a41dad98ea68a.js"&gt;https://gist.github.com/joshstrange/cfb4128f1be7ac60c22a41dad98ea68a.js&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;My hook also adds the push notification capability but that should be easy enough to remove if you don’t need it. You will need to edit the it to put your app name instead of “My App”. Once you have added that file to your hooks directory (or wherever you store your hooks) you will need to add the following line into your &lt;code&gt;config.xml&lt;/code&gt; file:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;hook src=&amp;#34;hooks/add_target_attributes_and_push_notification_cap.js&amp;#34; type=&amp;#34;after_prepare&amp;#34; /&amp;gt;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You do NOT want to put that inside your iOS platform block. It needs to be at the top level. For some reason Cordova hooks don’t fire inside the platform blocks unless the command you run explicitly mentions a platform. &lt;code&gt;ionic cordova platform add ios&lt;/code&gt; will trigger a hook inside the platform block but &lt;code&gt;ionic cordova prepare&lt;/code&gt; will not even if/when it adds the missing iOS platform.&lt;/p&gt;&#10;&lt;h2 id="add-platformsplatformsjson-to-repo"&gt;Add &lt;code&gt;platforms/platforms.json&lt;/code&gt; to repo&lt;/h2&gt;&#10;&lt;p&gt;Even though I had iOS and Android as engines in the &lt;code&gt;config.xml&lt;/code&gt; file Cordova would not pick them up when running &lt;code&gt;ionic cordova prepare&lt;/code&gt;. I had to edit my &lt;code&gt;.gitignore&lt;/code&gt; and add &lt;code&gt;platforms/platforms.json&lt;/code&gt; to my repo:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;platforms/*&#10;!platforms/platforms.json&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;then run&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add platforms/platforms.json&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="setup-your-fastfile"&gt;Setup your Fastfile&lt;/h2&gt;&#10;&lt;p&gt;Copy and paste the following Fastfile into &lt;code&gt;./fastfile/Fastfile&lt;/code&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://gist.github.com/joshstrange/a3da9901cc84ae7f5d0b1b40dd110e0f.js"&gt;https://gist.github.com/joshstrange/a3da9901cc84ae7f5d0b1b40dd110e0f.js&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;Search for “&lt;em&gt;REPLACE ME&lt;/em&gt;” for all the places where you need to sub in your own values.&lt;/p&gt;&#10;&lt;p&gt;Now you should be able to run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastlane ios beta&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OR &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fastlane android alpha&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To automatically build and upload to Testflight or Google Play Alpha&lt;/p&gt;&#10;&lt;h2 id="extra-gitlab-ci"&gt;Extra: Gitlab CI&lt;/h2&gt;&#10;&lt;p&gt;We use Gitlab CI and here is the .gitlab-ci.yaml file we use:&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://gist.github.com/joshstrange/b6948d95597a5e9e67623a913baebb50.js"&gt;https://gist.github.com/joshstrange/b6948d95597a5e9e67623a913baebb50.js&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>My experience with Coin and why it will ultimately fail</title><link>https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/</link><pubDate>Wed, 22 Jul 2015 13:37:54 +0000</pubDate><guid>https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/</guid><description>&lt;p&gt;Update: While my opinion of the Coin has not changed I just wanted to give Coin’s PR a shout out. &lt;a href="https://twitter.com/coin/status/623917626394308608"&gt;This is how you do PR&lt;/a&gt;. And for the record I don’t regret my decision to buy the Coin at all, it’s just not going to cut it for me but I wish Coin the best of luck.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://onlycoin.com"&gt;Coin, Use One Coin for All of Your Cards&lt;/a&gt;. If only that were true… If you add in a “sometimes” in there it’s closer though I honestly can’t even recommend for the “sometimes” use-case. Before we move on let me take a step back. Back in November of 2013 (2013-11-14 to be exact) I remember seeing &lt;a href="https://news.ycombinator.com/item?id=6733610"&gt;Coin posted to Hacker News&lt;/a&gt; and thinking it was a really neat idea. At the time it was only $50 to pre-order the Coin (Now $100 + Shipping + Tax ~ $117.50) and I was in the middle of my “kickstarter craze” and so I jumped on it. The idea of only carrying one card was highly attractive to me as I hate sitting on my wallet and was interested in some of the slim wallets or even the phone case + wallet combinations. Going from 6-8 credit/debit/HSA/gift cards to just 1 would have made that transition even easier or at the very least my wallet thinner.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/beta-back.jpg"&gt;&lt;img src="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/beta-back_hu_5c5107d9cbe723d0.webp" width="1600" height="1200" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;As with most crowdfunding projects I have backed, I more or less forgot about Coin, save for the every few months “What ever happened to that Coin thing?” thoughts followed by obsessively checking the website for the ship date for a few hours. This continued until February 14, 2014 when I got an email offering access to the beta of Coin which would mean I would get a Coin earlier PLUS I would get the final version when it shipped. I jumped at the chance (Who wouldn’t, it was a win-win), signed up, and waited for the beta. Ten months later (2014-12-21) I received an email informing me that my Coin beta had shipped and when I got back from Christmas vacation it was waiting in my mailbox.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/beta-front.jpg"&gt;&lt;img src="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/beta-front_hu_3fc8c268fe7eda8f.webp" width="1600" height="2133" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;I was ecstatic to get my Coin and I immediately set it up; I was a little disappointed that I couldn’t add my HSA card (the beta required micro-deposits to verify your card and it kept failing on my HSA card) but I got my credit and debit cards in more or less without issue (No indication of a “bad swipe in” was irritating but “It’s a beta”). From the moment I received my Coin, I used it as my primary card for everything (except opening a tab, for that I would leave a real card then pay with the Coin) as I’m a big believer in going “all in” when testing/trying something. I had about a 70% success rate with swiping the card (this is not per-swipe but per POS, sometimes I’d see the person swipe the card 2-3 times before it went through, but I chalked this up to the beta as well as POS POS machines) and overall I was happy, it was a beta after all, the final version said it would fix all the issue with the beta and I had no reason to disbelieve this claim.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/final-back.jpg"&gt;&lt;img src="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/final-back_hu_cf7f3a741ace408b.webp" width="1600" height="1200" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;5 months later, in May, I got an email letting me know that my final Coin was shipping and I was excited to put all of the problems I had experienced behind me as well as getting new features like getting a notification when you leave your Coin behind. My Coin arrived in early June and I was pleasantly surprised to find I no longer had to go through the micro-deposit confirmation step and I was able to add my HSA account and the card swiper (to add cards) seemed to work better. I used it the next morning in my work cafeteria and it worked fine (it had worked countless times before so this was no surprise) however before I got to my desk I got the “Did you leave your Coin behind?” push notification. I checked my wallet and the Coin was in there so I just shrugged it off and went on with my day. Unfortunately that wasn’t the last time I got that notification, in fact EVERY time I used my Coin 1-3 minutes later I would get that message even though there would be less than 1 foot between my wallet and phone.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/final-front.jpg"&gt;&lt;img src="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/final-front_hu_f9246ed0fc91d0ee.webp" width="1600" height="2133" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;I reached out to Coin support (after Googling and finding other users with the same issue) and their response was:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#34;I can totally understand that would be annoying and I know we are working on a fix to resolve that issue as other users are seeing same issue with lock &amp;amp; find telling them they left their phone behind even though it is in their pocket. The reason is because it is based of the amount of time you are away from your Coin device not the distance.&amp;#34;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This was followed by some copy-pasta giving an overview of the “Lock &amp;amp; Find” feature. Needless to say this didn’t really help me at all as my Coin had never been more than 1 foot for ANY amount of time. Shortly after this point even though I had only had the final version of Coin for about 3 weeks I decided to stop carrying my Coin.&lt;/p&gt;&#10;&lt;p&gt;The Coin was/is a very neat idea and if it worked you’d be reading about me singing its praises instead of putting it on a shelf to collect dust. The problem is it is so unreliable that you HAVE to carry a backup card with you at all time to the point that it’s just easier to use the backup instead of the Coin. I thought about continuing to carry the Coin as a backup-only (So main card + Coin only) but even then I’d need to carry a backup for the Coin and it’s just easier to carry 2-3 cards and no Coin. The Lock &amp;amp; Find features are neat but the Find feature is way too buggy and I’ve never lost my wallet (or a card) nor have I had any of my cards stolen so those features were less appealing to me. I’m not sure if it would have helped but I would have rather had a Coin that could only work with a handful of cards (Maybe no gift/loyalty cards?) if it worked 100% of the time but I doubt even giving up those things that it could get anywhere close to 100%.&lt;/p&gt;&#10;&lt;p&gt;I’m an early adopter, that’s just me, I can put up with a lot of shit if, and only if, I know it’s going to get better or I can see it getting better. That said, my experience from beta -&amp;gt; final was nearly identical (with the addition of the “Find” feature annoyance). Not a SINGLE place where it previously didn’t swipe with the beta worked with the final so from my perspective the final didn’t get me anything I really wanted. For 7 months I put up with “Sir, your card isn’t working, do you have another?” and put up with teasing from friends about my “Space Card” (Apparently Space == Future to them). At the start it was just funny but after a while it got really old and I’d find myself watching the waiter/waitress/bartender and getting my backup card ready to hand them after seeing them try 2-3 times to swipe before giving up.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/side-by-side.jpg"&gt;&lt;img src="https://joshstrange.com/2015/07/22/my-experience-with-coin-and-why-it-will-ultimately-fail/side-by-side_hu_e9596e7c495064b6.webp" width="1600" height="1200" alt="" loading="lazy" decoding="async"&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;It’s with no joy that I say that Coin “will ultimately fail”. It’s acceptance rate is unacceptable, it ships with extremely annoying bugs in major selling points, and at its core it doesn’t make my life easier, in fact it makes my life harder. I also don’t think Coin will be the only one to fail, I don’t have high hopes for &lt;a href="https://www.plastc.com/card"&gt;Plastc&lt;/a&gt; or any other Coin clones out there as I expect they will suffer from the same issues Coin did. Don’t get me wrong, this is not an easy problem to solve, there are probably thousands, if not more, of different types of POS systems out there and trying to support them all would appear to be a fool’s errand. Now Apple/Android/NFC pay is EXTREMELY interesting to me and the 3 times I’ve used Apple Pay I’ve found it to be very convenient so for now I’ll just stick to 2-3 cards that I carry until Apple/Android/NFC pay is more ubiquitous.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://news.ycombinator.com/item?id=9929531"&gt;Discuss on Hacker News&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Ubuntu, formatting a 3TB drive</title><link>https://joshstrange.com/2014/12/28/ubuntu-formatting-a-3tb-drive/</link><pubDate>Sun, 28 Dec 2014 19:13:42 +0000</pubDate><guid>https://joshstrange.com/2014/12/28/ubuntu-formatting-a-3tb-drive/</guid><description>&lt;p&gt;There was a blog post that I have used multiple times to format a drive at or over 3TB in size. Unfortunately the original blog post recently went offline and I had to use the wayback machine to get it’s contents. The original URL of the post is: &lt;a href="http://amaras-tech.co.uk/article/158/Ubuntu,_formatting_a_3TB_drive"&gt;here&lt;/a&gt; and a link to the wayback machine archive of it is: &lt;a href="https://web.archive.org/web/20130723053426/http://amaras-tech.co.uk/article/158/Ubuntu,_formatting_a_3TB_drive"&gt;here&lt;/a&gt;. If the original author puts the article back online I would be more than happy to link to it and remove the archived version I have posted below. I hope this helps other people as well but I am posting it here mainly as a guide for myself.&lt;/p&gt;&#10;&lt;p&gt;TL;DR:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;lsblk&lt;/code&gt; to find the drive name (will be something like &lt;code&gt;/dev/sdX&lt;/code&gt; where X is a letter)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo parted /dev/sdX&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;In the parted interactive console type:&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;(parted) mklabel gpt&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;(parted) unit TB&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;(parted) mkpart primary 0.00TB 3.00TB&lt;/code&gt; (Changing &lt;code&gt;3.00TB&lt;/code&gt; to the size of your drive)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;(parted) quit&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo mkfs.ext4 /dev/sdX1&lt;/code&gt; (Where X is the same as above and note the “1” now)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo tune2fs -m 1 /dev/sdX1&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;mkdir /Your/Mount/Point/Here&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo blkid&lt;/code&gt; (Copy the UUID of your drive)&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo nano /etc/fstab&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Add the line &lt;code&gt;UUID=YOUR-UUID-HERE-XXXX /Your/Mount/Point/Here ext4 defaults 1 2&lt;/code&gt; then save and exit&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;sudo mount -a&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Your drive will be partitioned, formatted as ext4, and mounted.&lt;/p&gt;&#10;&lt;p&gt;Below is the original article:&lt;/p&gt;&#10;&lt;p&gt;I started off following &lt;a href="https://web.archive.org/web/20130723053426/https://help.ubuntu.com/community/InstallingANewHardDrive"&gt;the ubuntu guide for installing a new drive&lt;/a&gt;, which uses fdsik to create the partitions how ever after formatting and mounting I ran &lt;code&gt;df -h:&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Filesystem Size Used Avail Use% Mounted on&#10;/dev/sdb1 2.0T 28G 1.9T 2% /mnt/kryten/disk5&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="which-is-not-3tb"&gt;Which is not 3TB&lt;/h2&gt;&#10;&lt;p&gt;I then found &lt;a href="https://web.archive.org/web/20130723053426/http://www.cyberciti.biz/tips/fdisk-unable-to-create-partition-greater-2tb.html"&gt;this article&lt;/a&gt; which worked in the long run not sure if I typo’d a command but similar to &lt;a href="https://web.archive.org/web/20130723053426/http://archlinuxarm.org/forum/viewtopic.php?t=1935&amp;amp;p=10490"&gt;this other user&lt;/a&gt; I had to run through the commands a second time for them to work.&lt;/p&gt;&#10;&lt;p&gt;To start with use &lt;code&gt;parted&lt;/code&gt; instead of fdisk, this allows the gpt partition table that fdisk does not.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;(parted) mklabel gpt &#10;Warning: The existing disk label on /dev/sdb will be destroyed and all data on&#10;this disk will be lost. Do you want to continue?&#10;Yes/No? yes &#10;(parted) unit TB &#10;(parted) mkpart primary 0.00TB 3.00TB &#10;Warning: You requested a partition from 0.00TB to 3.00TB. &#10;The closest location we can manage is 0.00TB to 0.00TB.&#10;Is this still acceptable to you?&#10;Yes/No? yes&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;basically after this point it showed up as a few KB after formatting, tried it again and it seemed to work much better.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;(parted) mklabel gpt &#10;Warning: The existing disk label on /dev/sdb will be destroyed and all data on&#10;this disk will be lost. Do you want to continue?&#10;Yes/No? yes &#10;(parted) unit TB &#10;(parted) mkpart primary 0.00TB 3.00TB &#10;(parted) print &#10;odel: Unknown (unknown)&#10;Disk /dev/sdb1: 3001GB&#10;Sector size (logical/physical): 512B/4096B&#10;Partition Table: loop&#10;&#10;Number Start End Size File system Name Flags&#10;1 0.00TB 3.00TB 3.00TB primary&#10;&#10;(parted) quit &#10;Information: You may need to update /etc/fstab.&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="format"&gt;Format&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo mkfs.ext4 /dev/sdb1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$sudo&lt;/span&gt; parted /dev/sdb1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;parted&lt;span class="o"&gt;)&lt;/span&gt; print&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Model: Unknown &lt;span class="o"&gt;(&lt;/span&gt;unknown&lt;span class="o"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Disk /dev/sdb1: 3001GB &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sector size &lt;span class="o"&gt;(&lt;/span&gt;logical/physical&lt;span class="o"&gt;)&lt;/span&gt;: 512B/4096B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Partition Table: loop&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Number Start End Size File system Flags&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1&lt;/span&gt; 0.00B 3001GB 3001GB ext4&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="reduce-reserved-space"&gt;Reduce Reserved Space&lt;/h2&gt;&#10;&lt;p&gt;By default 5% is reserved for the root to stop the driving becoming unusable when full, for data and not the main OS drive this does not matter too much, and 5% was decided when drives where much small. 5% of 3TB is 150 GB, way too much. Turn this down to 1% with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo tune2fs -m &lt;span class="m"&gt;1&lt;/span&gt; /dev/sdb1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="mount"&gt;Mount&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mkdir /mnt/kryten/disk5 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mount /dev/sdb1 /mnt/kryten/disk5&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="find-uuid"&gt;Find UUID&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo blkid&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/sda1: &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;c41cba49-bd3a-41d7-961c-b4ad45d48ed1&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ext4&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/sdb1: &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;d822c6af-9802-4d00-8de8-61f1653a854a&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ext4&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/sdc1: &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;59a61ceb-fee3-460a-97c5-e9f115776daf&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ext4&amp;#34;&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/sdc5: &lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;80f9c384-9a5a-4563-921a-5c25628e1b2e&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;swap&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="update-etcfstab"&gt;Update /etc/fstab&lt;/h2&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$sudo vim /etc/fstab&#10;&#10;#/dev/sdb1 3TB drive replacing 1.5TB old UUID Below&#10;UUID=d822c6af-9802-4d00-8de8-61f1653a854a /mnt/kryten/disk4 ext4 rw,auto,user,exec,async,errors=remount-ro 0 1&#10;#UUID=c41cba49-bd3a-41d7-961c-b4ad45d48ed1 /mnt/kryten/disk4 ext4 rw,auto,user,exec,async,errors=remount-ro 0 1&#10;&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>How to setup a dropbox-backed screenshot sharing website with your own domain on S3</title><link>https://joshstrange.com/2014/06/28/how-to-setup-a-dropbox-backed-screenshot-sharing-website-with-your-own-domain-on-s3/</link><pubDate>Sat, 28 Jun 2014 17:41:42 +0000</pubDate><guid>https://joshstrange.com/2014/06/28/how-to-setup-a-dropbox-backed-screenshot-sharing-website-with-your-own-domain-on-s3/</guid><description>&lt;p&gt;&lt;em&gt;Notice: At this time this setup is dependent on you installing a OS X app called UpShot. If you have windows/linux this guide will not work for you unless you use different software. If anyone that uses either of these OSes for personal computing would like to provide instructions for either/both then I will accept your pull request.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;TL;DR: Here is the &lt;a href="https://github.com/joshstrange/screenshot/blob/master/README.md"&gt;guide&lt;/a&gt; if you don’t wish to read the background.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;I recently had a good friend of mine, &lt;a href="https://github.com/sammarks"&gt;Sam Marks&lt;/a&gt;, show me a Dropbox-based screenshot sharing front-end that ran on his own domain. He had it setup so when he took a screenshot the URL dumped into his Copy/Paste buffer was his own domain (s.smarks.me). This was an idea that we had discussed a week or so ago but I had more or less forgotten about. All credit for the idea and 90%+ of the code goes to him, all I did was simply write up the instructions.&lt;/p&gt;&#10;&lt;p&gt;Here is the repo that he created: &lt;a href="https://github.com/sammarks/screenshot"&gt;sammarks/screenshot&lt;/a&gt;. I thought it was a really cool idea but didn’t like the idea of having to use PHP. This blog is hosted on S3 and whenever possible I like to host things on S3 because it means not paying for a server or not having multiple sites that might go down if there is an issue with the server they are on. I took a look at the index.php in his repo and quickly confirmed that this could be handled all in Javascript and set out to do just that.&lt;/p&gt;&#10;&lt;p&gt;It didn’t take long to get it converted to JS and to embed all the files needed into index.html so that there would only be a single file loaded (aside from the Dropbox image load). The design is very simple and clean but if you wanted to spruce it up it wouldn’t take much work to &amp;ldquo;make it your own&amp;rdquo;. As my screenshots are randomly named it didn’t sense to show the title of the image in the header of the page like Sam did in his so mine simply says &amp;ldquo;Screenshot&amp;rdquo; right now. Feel free to remove the header or change it to whatever you want if you wish.&lt;/p&gt;&#10;&lt;p&gt;Without further ado here is the guide to set this up on your own AWS account: &lt;a href="https://github.com/joshstrange/screenshot/blob/master/README.md"&gt;Github Readme&lt;/a&gt;. If you have any problems &lt;a href="https://github.com/joshstrange/screenshot/issues"&gt;file a ticket&lt;/a&gt; on Github.&lt;/p&gt;&#10;</description></item><item><title>Why it’s a bad idea to put a CNAME record on your root domain</title><link>https://joshstrange.com/2014/02/24/why-its-a-bad-idea-to-put-a-cname-record-on-your-root-domain/</link><pubDate>Mon, 24 Feb 2014 19:05:40 +0000</pubDate><guid>https://joshstrange.com/2014/02/24/why-its-a-bad-idea-to-put-a-cname-record-on-your-root-domain/</guid><description>&lt;p&gt;When I first setup this blog on AWS S3 I wanted to use my “naked domain” name. What that means is instead of visitors going to &lt;a href="http://www.joshstrange.com"&gt;www.joshstrange.com&lt;/a&gt; I wanted them to be able to go to &lt;a href="http://joshstrange.com"&gt;joshstrange.com&lt;/a&gt;. It just seemed cleaner to me and it was fairly easy to get up and running. To accomplish this I setup 2 CNAME records one on joshstrange.com and the other on &lt;a href="http://www.joshstrange.com"&gt;http://www.joshstrange.com&lt;/a&gt; with each pointing to a different bucket, the “www” bucket was setup to redirect to &lt;a href="http://joshstrange.com"&gt;joshstrange.com&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Everything was working just fine, or so I thought, and so I forgot all about it and moved on with my life. A few months later when this was but a distant memory I started having problems with my &lt;a href="https://us.etrade.com/home"&gt;eTrade&lt;/a&gt; account. I was unable to receive any emails from them and so I was unable to add a new bank account to withdraw my money. I was getting all of their marketing emails just fine but the email confirmations for adding a new bank account just never showed up. I called into support and went through the “Did you check your spam folder” song and dance until the representative told me he could just do a wire transfer to get me the money and he would waive the fee this time. I thought this was strange but I got the money and once again I forgot all about it.&lt;/p&gt;&#10;&lt;p&gt;Fast-forward a couple more months and I had decided to pull all of my money out of eTrade and start using &lt;a href="https://www.tradeking.com/"&gt;TradeKing&lt;/a&gt;&lt;br&gt;&#10;I quickly ran into the email issue again on eTrade and decided to try switching my email address from my Google Apps account to my old GMail account. I thought that surely this couldn’t be affecting GMail or more people would be complaining and the problem would have been fixed. Sure enough as soon as I changed the email address it started working. Again, I got the money and promptly forgot about the whole ordeal writing it off as some problem on eTrade’s end.&lt;/p&gt;&#10;&lt;p&gt;Then just this last weekend I went to signup for an &lt;a href="http://www.ebay.com/"&gt;eBay&lt;/a&gt; Developers account so I could play with the API but the email confirmation never came through. At this point I remembered that my actual eBay account was still using an old email address because even though I added my &lt;a href="mailto:Josh@Joshstrange.com"&gt;Josh@Joshstrange.com&lt;/a&gt; email address I had never received the confirmation email. I finally decided to really look into this email issue and try to figure out what the cause was. After an hour of Googling every combination of “eBay not receiving email google apps” and switching out “eBay” for “eTrade” I stumbled across someone else having a similar problem and one of the suggestions was to take a look at their DNS records. I ignored it the first time I read it but then I saw it mentioned again and this time in combination with CNAME records.&lt;/p&gt;&#10;&lt;p&gt;Something clicked in my head and I started to vaguely remember reading something when first setting up my blog about how CNAME records don’t belong on your root domain. When I had read that I had written it off as I did not fully understand what it was saying at the time. Now, armed with this revelation, I started searching for “CNAME and MX conflicts” and found countless people warning against it. This ServerFault &lt;a href="https://serverfault.com/questions/91712/dns-using-cnames-breaks-mx-records"&gt;question&lt;/a&gt;/&lt;a href="https://serverfault.com/a/91718/138913"&gt;answer&lt;/a&gt; sums it up nicely:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;This is a common error. You cannot use a CNAME RR for your root domain (e.g. company.com) and define additional resource records for the same zone.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;See &lt;a href="https://support.dnsmadeeasy.com/index.php?%5Fm=knowledgebase&amp;amp;%5Fa=viewarticle&amp;amp;kbarticleid=14"&gt;Why can’t I create a CNAME record for the root record?&lt;/a&gt; and &lt;a href="http://www.faqs.org/rfcs/rfc1034.html"&gt;RFC1034&lt;/a&gt; section 3.6.2 for details:&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;If a CNAME RR is present at a node, no other data should be present; this ensures that the data for a canonical name and its aliases cannot be different.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;So my own stupidity had caused this issue but I still wasn’t sure how to go about fixing the problem. My first step was to delete the CNAME DNS record in &lt;a href="https://www.cloudflare.com/"&gt;CloudFlare&lt;/a&gt; to make sure this was the cause. As soon as I could see the record was gone (Using &lt;code&gt;dig joshstrange.com&lt;/code&gt;) I attempted to resend my eBay Developer activation email and it came through instantly. I have yet to test with eTrade as they don’t allow you to withdraw money for 15 days after you change your email address and I still have some stock tied up there.&lt;/p&gt;&#10;&lt;p&gt;So now my email was working but trying to go to &lt;a href="http://joshstrange.com"&gt;joshstrange.com&lt;/a&gt; was broken and going to &lt;a href="http://www.joshstrange.com"&gt;www.joshstrange.com&lt;/a&gt; was redirecting to the naked domain. I started to look for solutions and was expecting to have to start using my domain with the “www” until I stumbled across &lt;a href="http://aws.typepad.com/aws/2012/12/root-domain-website-hosting-for-amazon-s3.html"&gt;this&lt;/a&gt; AWS Blog on how to do exactly what I was wanted. The only “problem” was I would have to use Route 53 instead of CloudFlare for it to work. For me this wasn’t a hard decision, I already use AWS for a lot of stuff and it just made sense to have my domain’s DNS hosted with them. For other’s this may not be an option and you might want to check out &lt;a href="http://wwwizer.com/naked-domain-redirect"&gt;wwwizer’s naked domain redirect&lt;/a&gt; which lets you add an A record to your root pointing to 174.129.25.170 and any request to yourdomain.com will be re-routed to &lt;a href="http://www.yourdomain.com"&gt;http://www.yourdomain.com&lt;/a&gt; (the opposite of what I was looking for).&lt;/p&gt;&#10;&lt;p&gt;Want to know if your site is using CNAME and MX records on your root domain? Test it out by entering your domain name in &lt;a href="http://mxtoolbox.com/"&gt;MX Toolbox&lt;/a&gt;. MX Toolbox, if you didn’t know is an awesome tool for checking your DNS records, I’ve used it extensively to troubleshoot issues and double check things and can’t recommend it enough (Yes &lt;code&gt;dig&lt;/code&gt; is great but I often want to “sanity-check” that what I see locally is what everyone else is seeing as well).&lt;/p&gt;&#10;</description></item><item><title>Playing around with GoAngular</title><link>https://joshstrange.com/2013/10/15/playing-around-with-goangular/</link><pubDate>Tue, 15 Oct 2013 17:33:14 +0000</pubDate><guid>https://joshstrange.com/2013/10/15/playing-around-with-goangular/</guid><description>&lt;p&gt;UPDATE 12/23/13&lt;/p&gt;&#10;&lt;p&gt;On December 21st, 2013 Firebase reached out to me because of this blog post to ask for what exactly I found more desirable in goAngular/goInstant over AngularFire/Firebase and to point out the updated documentation for AngularFire which has really simplified the API. Not only has the API been cleaned up but so has the &lt;a href="http://angularfire.com/documentation.html"&gt;documentation&lt;/a&gt; itself, it is much easier to follow and use as a reference instead of discerning it just from examples/minimal docs. I plan on playing with again to see how it compares to goInstant and possibly writing a more in-depth review of the two as more of a side-by-side comparison. I would like to thank James at Firebase for reaching out to me to ask for feedback, it’s always so refreshing to see a company reach out to people who aren’t satisfied with their product and I appropriate the directness. The BaaS game is just starting to really heat up and I look forward to not only goInstant’s and Firebase’s future innovations in the field but also other startups as I’m sure we have only seen the tip of the iceberg.&lt;/p&gt;&#10;&lt;p&gt;—&lt;/p&gt;&#10;&lt;p&gt;As of late I have been very interested in Real-time communication(RTC)/BaaS providers such as &lt;a href="https://www.firebase.com/"&gt;Firebase&lt;/a&gt;, &lt;a href="http://www.kinvey.com/"&gt;Kinvey&lt;/a&gt;, and &lt;a href="https://goinstant.com/"&gt;GoInstant&lt;/a&gt;. I realize that not all of those are RTC nor are they all BaaS but they all allow you to get up and running without having to setup a server. You can signup, download the sample app, and be playing with it in record time. I get extremely excited just thinking about the possibilities these platforms offer and love how time to MVP can be greatly decreased.&lt;/p&gt;&#10;&lt;p&gt;Along with RTC/BaaS I have also been spending quite a bit of my time working with &lt;a href="http://angularjs.org/"&gt;AngularJS&lt;/a&gt;, both for my job and in my free time. If you haven’t heard about AngularJS or have never used it allow me to understate it’s coolness with this short description: AngularJS is a JS framework that, unlike most JS frameworks, allows for 2-way binding meaning if I bind a variable to an input box and then change the value of the variable in my code then the value in the input box will auto-update and if I change the value of the input box then the value in my code will also be changed (See an example &lt;a href="http://angularjs.org/#the-basics"&gt;here&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;About a week ago I was contacted by an employee at GoInstant asking me to test out their GoAngular library they wrote to make AngularJS development even easier. I had been working with &lt;a href="http://angularfire.com/"&gt;AngularFire&lt;/a&gt; quite a bit which I was using to create a chat application for work and so I was excited to test out another similar framework. I had tried out Kinvey for a side-project of mine but it didn’t have AngularJS bindings and so I had to write my own wrapper services to &amp;ldquo;Angularize&amp;rdquo; some of their code. I had somehow missed GoInstant when I went on my search for BaaS providers, probably because my requirements at the time did not include RTC, and so I jumped at the chance to play with a new, to me, service (especially since I got to use AngularJS).&lt;/p&gt;&#10;&lt;p&gt;Looking back I really wish I had been contacted about 1 month earlier as in my opinion GoInstant is better than Firebase in nearly every aspect when it comes to what I am doing with it. It has a much finer-grained level of control than Firebase and would have suited my project better. The only thing I like more about Firebase is Firebase Forge which is a very nice web-gui interface to easily see and manage your data. GoInstant, due to it’s nature, doesn’t really need something like this but it might be nice for debugging purposes. GoInstant and I traded a few emails and I gave some feedback on the library and today they are releasing it to the public. Check out their blog post &lt;a href="https://goinstant.com/blog/new-release-angularjs--goinstant--goangular-"&gt;here&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://developers.goinstant.com/v1/GoAngular/index.html"&gt;GoAngular&lt;/a&gt; is a really cool library that will get you up and running with GoInstant’s back-end in no time. Here is their &lt;a href="https://developers.goinstant.com/beta/GoAngular/getting_started.html"&gt;FormSync&lt;/a&gt; example on &lt;a href="http://embed.plnkr.co/7bZm4Imq6yT9JhOndsTP/preview"&gt;Plnkr&lt;/a&gt; (Open 2 tabs with it to see it syncing).&lt;/p&gt;&#10;&lt;p&gt;GoAngular takes an interesting approach in that by default your are synchronizing your entire controller’s &lt;code&gt;$scope&lt;/code&gt; instead of a specific variable. When initializing GoAngular you can choose to include or exclude models by passing in an array of names or regular expression patterns to match against. Here is a &lt;a href="http://embed.plnkr.co/BkijiQSEEEewK2FiAJiZ/preview"&gt;simple chat applicationn&lt;/a&gt; I threw together in a few minutes. I’ve quite enjoyed working with GoAngular and I look forward GoInstant’s continued development on it.&lt;/p&gt;&#10;</description></item><item><title>First thoughts on iOS 7</title><link>https://joshstrange.com/2013/07/02/first-thoughts-on-ios-7/</link><pubDate>Tue, 02 Jul 2013 12:47:32 +0000</pubDate><guid>https://joshstrange.com/2013/07/02/first-thoughts-on-ios-7/</guid><description>&lt;p&gt;After watching WWDC’s keynote I couldn’t wait to get my hands on iOS 7. I was able to hold off for just under 2 weeks before I broke down and renewed my iOS developer license. I had been planning on playing with some HTML5/CSS3/JS -&amp;gt; iPhone platforms again which is how I justified it to myself. I downloaded the beta and restored my phone and here is what I found.&lt;/p&gt;&#10;&lt;p&gt;This is probably obvious to most iOS devs and probably the majority of people but even though I have installed betas in the past I never remember them completely wiping all my apps forcing me to re-download them all. It wasn’t particularly hard to go to the purchased section in the App Store and re-download my apps and I took this opportunity to be a little picker about which apps I installed. At this point I had found a few little bugs here and there, mostly graphical glitches or the app freezing up.&lt;/p&gt;&#10;&lt;p&gt;Fast-forward a few days and overall I was happy with the new OS but on my iPhone 4S it was definitely sluggish and sometime completely unresponsive. I have a friend who installed the beta on an iPhone 4 and I can’t imagine how bad that must be. Sometimes I will try to click on something and it just won’t respond. Sometimes I even have to do a reset (hold home + power button till it reboots) to get the phone back into working condition. This was infuriating when it happened and it would happen when unlocking the phone, in the app switcher, and sometimes after waking the phone the running app would be unresponsive. The worst was getting stuck between the lock and homescreen (during unlock) because the &amp;ldquo;depth perception&amp;rdquo; would still work but tapping the screen/pushing the home/power button has no effect so you could tilt the phone ever which-way to see the cool depth effect but you couldn’t do anything else.&lt;/p&gt;&#10;&lt;p&gt;I upgraded to beta 2 the day it came out and since I only had beta 1 for about 2 days before beta 2 I can’t really speak to the changes between the versions. Unfortunately the Audible app does not work on iOS 7 yet (it crashes while trying to refresh your library) which means I have been carrying my iPad Mini around so I can listen to my book. There are multiple apps (PvZ being another one which has left me searching for another game to play) that don’t work on iOS 7 yet which is to be expected at this stage. I’m sure 90% of the apps I use will work just fine when iOS 7 is released.&lt;/p&gt;&#10;&lt;p&gt;Overall I am very pleased with the OS. When it works it is amazing but when it doesn’t I just want to throw it out a window. It transitions from being an extension of you into a pretty paperweight in record time but as I said above, I expect all of this addressed before the release. Even if it’s not I plan on buying the iPhone 5S (Or whatever they decide to call it) the day it releases because my contract is up so I shouldn’t have to deal with any of the performance issues I am seeing on my 4S. I look forward to the future betas and the final release, regardless of what others are saying about iOS 7 I think it is a step in the right direction.&lt;/p&gt;&#10;</description></item><item><title>Short review of Rdio</title><link>https://joshstrange.com/2012/10/29/short-review-of-rdio/</link><pubDate>Mon, 29 Oct 2012 19:44:55 +0000</pubDate><guid>https://joshstrange.com/2012/10/29/short-review-of-rdio/</guid><description>&lt;p&gt;I listen to music just about all day long at work so whatever I use has to work well. Rdio, does not. I first heard about Rdio about 3 years ago when they were still in private beta, I didn’t get in. A year or so later when they opened to the public I wasn’t very interested due to having looked at Spotify and decided the streaming music wasn’t for me. Then about a month ago I got sick of having to constantly download new music that I wanted to listen to and started looking around again. A couple guys at work used Rdio and spoke highly of it so I went ahead and bought the $10/mo plan so I could use it on my phone/desktop/web. I have been using Rdio for about a month and here are the issues I have noticed with the desktop/mobile client:&lt;/p&gt;&#10;&lt;p&gt;Desktop:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;If the network ever drops or has any minor issues then Rdio skips to the next song. This is highly annoying, it should do a better job of buffering and handling network hiccups instead of giving up and moving on to the next song.&lt;/li&gt;&#10;&lt;li&gt;Often the interface becomes unresponsive and I have to either reopen the app or right-click -&amp;gt; reload .&lt;/li&gt;&#10;&lt;li&gt;The interface is slow even when the network is working perfectly, this is due to the &amp;ldquo;app&amp;rdquo; essentially just being the webpage in a webview wrapper. FB was guilty of this on iOS with their app as well, Rdio: Bite the bullet and write a real app please&lt;br&gt;&#10;Mobile:&lt;/li&gt;&#10;&lt;li&gt;The app does not &amp;ldquo;remember&amp;rdquo; if you have enabled shuffle/loop.&lt;/li&gt;&#10;&lt;li&gt;The app does not &amp;ldquo;remember&amp;rdquo; if you have enabled shuffle/loop.&lt;/li&gt;&#10;&lt;li&gt;The app does not &amp;ldquo;remember&amp;rdquo; if you have enabled shuffle/loop, oh oops, I forgot I had already typed that… But this is REALLY annoying, if I am quickly scanning through my playlist in my car and I hit the end of the playlist then I have to unlock my phone, launch the Rdio app if it’s not already open, select playlists, and click a song to get music playing again. A) This is not safe to do while driving and B) It is a terrible user experience, the least they could do is have the playlist open when I open the app&lt;/li&gt;&#10;&lt;li&gt;The shuffle &amp;ldquo;feature&amp;rdquo; is horribly broken. It shuffles in both directions so if I have shuffle on and I am trying to find a song I want to listen to but I click &amp;ldquo;Next&amp;rdquo; and then decide I DO want to listen to that song I cannot hit &amp;ldquo;Prev&amp;rdquo; to get back to the song. The thing that drives me nuts about this is the desktop version of shuffle works the way you expect and are used to.&lt;/li&gt;&#10;&lt;li&gt;Whenever you hit &amp;ldquo;Play&amp;rdquo; after having it paused and disconnected then it restarts the song so when I get out of my car then get back in and hit play the song restarts. Maybe this doesn’t bother other people but when I listen to 90% of a song on the way to work and then get in my car after work and it is restarted I get annoyed, the least they could do is just skip to the next song.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;I am very lukewarm on the idea of subscription based music and before Apple started to allow background music from apps I wouldn’t have gone anywhere near it. However I do enjoy the community playlists, I am extremely lazy so I really like subscribing to a playlist that someone else took the time to make, that said I have a complaint with Rdio’s playlists. You either have to love the entire playlist or tough luck. There is no way to &amp;ldquo;clone&amp;rdquo; or &amp;ldquo;copy&amp;rdquo; a playlist, I want this functionality so that I can take a popular playlist that is 85% what I want and remove some songs and add others. Really I would like the ability to &amp;ldquo;branch&amp;rdquo; a playlist and remove/add songs but still have new songs that are added to the original playlist added to my &amp;ldquo;branch&amp;rdquo;, of course if I don’t like the song I could remove it.&lt;/p&gt;&#10;&lt;p&gt;Tonight after work I will canceling my Rdio subscription and signing up for Spotify to see if the grass is any greener.&lt;/p&gt;&#10;</description></item><item><title>Alfred Extension: Unmount</title><link>https://joshstrange.com/2012/06/27/alfred-extension-unmount/</link><pubDate>Wed, 27 Jun 2012 18:04:02 +0000</pubDate><guid>https://joshstrange.com/2012/06/27/alfred-extension-unmount/</guid><description>&lt;p&gt;This is just a quick little post about a small &lt;a href="http://www.alfredapp.com/"&gt;Alfred&lt;/a&gt; extension I put together that lets you easily unmount all of your drives. I take my laptop and one of two of my external drives back and forth with me to work so I am always ejecting them, I wanted a fast and easy way to disconnect them that didn’t require me opening up finder and scrolling down to click the eject buttons.&lt;/p&gt;&#10;&lt;p&gt;I bought a small App Store app, &lt;a href="http://flyingpapersoftware.com/"&gt;unDock&lt;/a&gt; ($1), which worked perfectly and even had the ability to set a keyboard shortcut to unmount all your drives. However, I wanted something that would work across all the macs I work on without me having to log into the app store on each one. Yes I am aware they will all need to have Alfred on them but most of the computers I use have Alfred on them so this is not an issue for me.&lt;/p&gt;&#10;&lt;p&gt;On that note I give you &lt;a href="https://www.dropbox.com/s/r5jrzu5qs8l0udc/Unmount.alfredextension"&gt;Unmount&lt;/a&gt;, a very simple Alfred (Applescript) extension that will unmount all your attached drives except your network and optical drives. All you have to do is type &amp;ldquo;unmount&amp;rdquo;, or whatever keyword you choose to use, and all of your drives will be ejected. It’s that simple, really thats all I’ve got, enjoy!&lt;/p&gt;&#10;</description></item></channel></rss>