{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "zmknox",
  "language": "en",
  "home_page_url": "https://zmknox.com/",
  "feed_url": "https://zmknox.com/feed.json",
  "description": "",
  "authors": [
    {
      "name": "zmknox"
    }
  ],
  "items": [
    {
      "id": "https://zmknox.com/2025/03/i-am-visible/",
      "url": "https://zmknox.com/2025/03/i-am-visible/",
      "title": "I Am Visible",
      "content_html": "<p>Today is the <a href=\"https://en.wikipedia.org/wiki/International_Transgender_Day_of_Visibility\">Transgender Day of Visibility</a>, and, for the first time, I am visible.</p>\n<!-- more -->\n<p>Hi, I’m Zoe 👋. I use she/her pronouns, and I’m a trans woman.</p>\n<p>I’m also a software developer and big computer nerd. If you don’t know me, I build JavaScript tools for work, have made a few neat websites on the side, dabbled with some iOS apps here and there, and listen to a bunch of technology podcasts. And I don’t expect any of that to be changing any time soon.</p>\n<p><img src=\"https://zmknox.com/resources/2025.jpg\" alt=\"Photo of me! A selfie of a white trans woman with long dark blonde hair and brown eyes wearing a scoop-neck tee smiling and sitting in front of a window with dark curtains.\" :=\"\" class=\"img-thumbnail img-thumbnail-smaller mx-auto d-block\"></p>\n<p>It’s taken me a long time to get to this point, and there’s still plenty that I have yet to do, but I’m so glad that I feel ready to be public about this important part of myself. I’m not gonna get all philosophical with this coming out post<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2025/03/i-am-visible/#fn1\" id=\"fnref1\">[1]</a></sup>, because frankly I don’t really have much more to say beyond “hey I’m a girl!”. But it really is freeing to not have to to hide a part of me away anymore.</p>\n<p>This is, unfortunately, not a particularly great time to be trans in the United States<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2025/03/i-am-visible/#fn2\" id=\"fnref2\">[2]</a></sup>. Our government is pushing order after order to strip us of our rights and treat us as second class beings, and we cannot let this stand. We must be visible and stand against this bigotry. If you’re cis, <em>please</em> be loud in support of trans folks, we really need it.</p>\n<p>Next time I post here<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2025/03/i-am-visible/#fn3\" id=\"fnref3\">[3]</a></sup> I’m sure I’ll be writing about computers again, but thanks for reading this very personal note. And an extra special thanks to my friends and family who have been so supportive of me throughout my transition. Y’all are the best.</p>\n<div class=\"big-text\">\n<p>🏳️‍⚧️❤️</p>\n</div>\n<hr>\n<p><em>P.S. If you’re trans or questioning your gender, feel free to send me a message if you wanna chat. I’m always happy to talk with other trans folks. (I know this can be especially scary in the early stages—I’ve been there—but I promise I don’t bite). We have to support each other!</em></p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>If that’s what you’re looking for, check out <a href=\"https://www.youtube.com/watch?v=AITRzvm0Xtg\">Abigail Thorn’s incredible coming out video</a> from a few years ago. <a href=\"https://zmknox.com/2025/03/i-am-visible/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>If you’re interested in keeping up with trans news, legislation, rulings, etc, I <em>highly</em> recommend following Erin Reed’s site <a href=\"https://www.erininthemorning.com\">Erin In The Morning</a>. She and her team do an incredible job at following and reporting on trans topics (and also helped me find a trans kid to buy Girl Scout cookies from). <a href=\"https://zmknox.com/2025/03/i-am-visible/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>Speaking of “here”, you can reach this site by visiting <a href=\"https://zoeknox.com/\"><strong>zoeknox.com</strong></a> too! You’ve actually been able to do that for a few months I think. <a href=\"https://zmknox.com/2025/03/i-am-visible/#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2025-03-31T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2025/02/welcome-to-my-new-new-website/",
      "url": "https://zmknox.com/2025/02/welcome-to-my-new-new-website/",
      "title": "Welcome to my new new website!",
      "content_html": "<p>Hello and welcome to my <em>NEW</em> new website. If everything goes well, it should look almost exactly the same<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2025/02/welcome-to-my-new-new-website/#fn1\" id=\"fnref1\">[1]</a></sup>.</p>\n<p>Around nine years ago, I set this website up with <a href=\"https://jekyllrb.com\">Jekyll</a> and <a href=\"https://pages.github.com\">GitHub Pages</a>. Since then, it’s gone to a new host (I’m currently hosting it using <a href=\"https://pages.cloudflare.com\">Cloudflare Pages</a>), switched primary domain names (though most old links should still redirect), and now is undergoing a brain transplant. From Jekyll to <a href=\"https://www.11ty.dev\">Eleventy</a>!</p>\n<!-- more -->\n<p>So here’s the deal: Jekyll is a great tool, but I could basically use it at the surface level. I did some cool stuff, like making my <a href=\"https://zmknox.com/projects\">projects</a> &amp; (secret) <a href=\"https://zmknox.com/tv\">TV</a> pages data driven and adding “link post” support to this blog, but those were all playing within the sandbox of Jekyll and Liquid as provided. So when the sandbox broke, I panicked.</p>\n<p>Last month, I thought to add that nice new little footer text you see down there after my copyright and privacy link. I tried to run a build locally, and my Ruby configuration complained that I had something messed up. I threw out the lockfile (as I would do in a Node.js project) and started over, and things seemed OK… until I went to publish those changes. Cloudflare complained about my configuration and wouldn’t build it.</p>\n<p>And look, it’s probably a silly little issue that anyone who knows Ruby could fix in like 15 minutes or less. But I know nothing about Ruby. I write JavaScript and TypeScript for my day job, and some Swift here and there—but I’ve never even touched Ruby (as much as some of my Rails convert friends may have wanted me to in college).</p>\n<h3>Eleventy?</h3>\n<p>I ultimately went with using Eleventy as my Jekyll replacement for two reasons. First, it’s JavaScript based, which means I, a professional JavaScript developer, am much more likely to be able to debug anything that may go wrong. Second, my internet pal <a href=\"https://rknight.me\">Robb Knight</a> has been singing Eleventy’s praises for a while now. Robb seems pretty smart, so I took his recommendation.</p>\n<p>My experience with configuring my site has been <em>mostly</em> smooth<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2025/02/welcome-to-my-new-new-website/#fn2\" id=\"fnref2\">[2]</a></sup>, but I had to change a bit about how I think about the site. Eleventy is a more minimal in its base package. It expects you to extend it with separate npm packages if you need extra functionality, like special markdown parsing. But extending base Eleventy is actually quite easy.</p>\n<p>For example: On my old site, my RSS and JSON feeds were made using files with liquid templates. It kind of felt like a hack, but it worked. Thankfully, with Eleventy, there’s a better way. I can use Eleventy’s <a href=\"https://www.11ty.dev/docs/plugins/rss/\">RSS plugin</a> to have it generate a proper feed for me with a few settings in my configuration file.</p>\n<p>My site doesn’t yet do much particularly complicated with these abilities yet, since my initial priority was just “make my website work again”. But I’m excited to have the opportunity to have more capability looking forward.</p>\n<p>I haven’t really used Eleventy enough to truly be able to give it a rating or recommend others move their sites over to it, but it seems pretty solid so far. If you’re looking for a new static-site generator for your site, and you feel comfortable with JavaScript, it’s definitely worth a look.</p>\n<h3>Onward</h3>\n<p>I’m sure I’ll have at least one more blog post here this year. But in the meantime, thanks for checking out my <em>new</em> website!</p>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>I did make a couple of design tweaks. I tweaked the navbar to use the previously mobile-only layout everywhere, and made my current projects show one entry per row. But it’s like 95% the same. <a href=\"https://zmknox.com/2025/02/welcome-to-my-new-new-website/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>The worst issue I ran into was an issue where all the dates rendered on my website were off by one. This was because it assumed a date without atime was midnight UTC. Since I live in the US, that meant that the value it rendered was the date prior, as it wanted to render a local date. Eleventy <a href=\"https://www.11ty.dev/docs/dates/#dates-off-by-one-day\">lists this as a “common pitfall”</a>, but I tend to think it’s a bad default. I have no idea what the time zone is of the Cloudflare server that will render my site. The render behavior of my local build should not be different from my server based on the time zone of my local machine. <a href=\"https://zmknox.com/2025/02/welcome-to-my-new-new-website/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2025-02-22T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/",
      "url": "https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/",
      "title": "Vision Accessibility on Apple&amp;nbsp;Vision&amp;nbsp;Pro",
      "content_html": "<p>I have low vision. A kind you can’t really correct for with glasses or contacts. I also bought Apple Vision Pro at launch. <em>Why would I do this?</em> Well because I’m a nerd who wants to see the future, but also because I was fascinated to see how Apple would handle accessibility for this new product. Apple’s track record on accessibility in the past decade has been stellar<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn1\" id=\"fnref1\">[1]</a></sup>, in my opinion, with their teams adding <a href=\"https://www.apple.com/newsroom/2023/05/apple-previews-live-speech-personal-voice-and-more-new-accessibility-features/\">powerful</a> <a href=\"https://www.apple.com/newsroom/2022/05/apple-previews-innovative-accessibility-features/\">options</a> <a href=\"https://www.apple.com/newsroom/2021/05/apple-previews-powerful-software-updates-designed-for-people-with-disabilities/\">every year</a> and ensuring every new platform has accessibility support built in from the start.</p>\n<p>After watching <a href=\"https://developer.apple.com/wwdc23/10034\">Apple’s WWDC23 session on visionOS accessibility</a>, I knew accessibility on visionOS was an important point for them. But even after consuming as much information on the platform as I could, I knew I had to try it for myself to know the answer to the important question: how well does it work <em>for me</em>?</p>\n<!-- more -->\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/accessibility-settings.png\" alt=\"A screenshot of the Accessibility settings in visionOS. Options can be seen in the Vision heading for VoiceOver, Zoom, Display and Text Size, Motion, Spoken Content, Audio Descriptions (set to off), and Eye Input (set to Both Eyes). A Physical and Motor heading is visible at the bottom of the window, hinting at scrollable content.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><em><strong>n.b.</strong></em> Before I begin, I want to make clear that these are only <em>my</em> impressions. There are many different ways vision impairments and disabilities can affect how someone experiences technology and the world, and I can only speak to my own experiences.</p>\n<h1>Input</h1>\n<p>On any computer system, how you interact with it is critical, and it’s one of the things that sets Apple Vision Pro apart from other devices (and even other VR headsets). Apple’s tech specs page describe input as relying on “Eyes, Hands, and Voice”, so let’s start there.</p>\n<h3>Eyes</h3>\n<p>This is probably the biggest input innovation in the product—you just look directly at what you want to select! (I recommend watching Apple’s <a href=\"https://www.youtube.com/watch?v=Vb0dG-2huJE\">guided tour video</a> if you aren’t familiar with how this works). But that paradigm can quickly break down with various eye conditions. One aspect of my vision is nystagmus, or uncontrollable eye movements. I generally don’t actively notice this, apart for possibly having more trouble focusing quickly on some things at times. But I could very quickly tell that it would cause Apple’s eye tracking system to work less than ideally (Apple does <a href=\"https://support.apple.com/en-us/HT213965\">specifically call out</a> that users with nystagmus may have issues).</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/eye-setup.png\" alt=\"A screenshot of eye setup on visionOS. Six dots are arranged in a hexagon pattern around text reading &quot;Now select all the dots around the circle&quot;. The background is a very dimmed view of passthrough to the real world.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Eye Setup in visionOS requires you to look at and select dots in three different lighting conditions. The darkest lighting condition is pictured here.</h6>\n<p>Some things work great. I can very easily select an app from the Home View or scroll the massive <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/%5Btext%5D(https:/apps.apple.com/us/app/runestone-text-editor/id1548193893)\">Runestone</a> window that I’m typing these words into right now. But it doesn’t take long to encounter issues. For me, that generally involves having many selection targets close together. That includes many toolbars (eg getting to the “Tab Overview” button in Safari), the virtual keyboard (when using look-and-tap), and even, at times, the close window button.</p>\n<p><video loop=\"\" autoplay=\"\" muted=\"\" controls=\"\" playsinline=\"\" preload=\"auto\" class=\"img-fluid\" src=\"https://zmknox.com/resources/vision-accessibility/focus-issues.mov\" title=\"Video showing the visionOS focus highlight jumping between the new tab button and the tab overview button in Safari\"></video></p>\n<h6>Can you tell which button I’m trying to look at?</h6>\n<p>That said, it’s far from unusable. I can still generally get around most things OK, but it’s clear that some interactions are harder for me than they are for others.</p>\n<p>I also generally wear tinted contact lenses to help control the amount of light going into my eyes<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn2\" id=\"fnref2\">[2]</a></sup>. When purchasing Apple Vision Pro, you’re presented with a series of questions about your vision and whether you wear glasses or contacts. One of these asks about whether you use contacts with a tint. If you say yes, it tells you that you can’t use these with Apple Vision Pro. But I’ve found that’s not entirely true.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/purchasing.png\" alt=\"A screenshot from the Apple Store app on iOS. &quot;Do your contacts have any cosmetic features? Cosmetic features can include tints and patterns that change or enhance the appearance of your eyes, and they are not compatible with Apple Vision Pro.&quot; Buttons are available for Yes and No.\" :=\"\" class=\"iphone-image mx-auto d-block\"></p>\n<p>I’ve tried using my Apple Vision Pro both with and without my contacts, and can get the eye tracking to work fine with either… with a catch. Using my calibration from one configuration doesn’t work properly with the other (I could launch some apps from the Home View, but not much else). So if I last used it with my contacts, I need to re-run eye setup to use it without them. Luckily, there’s a shortcut for this. Quadruple-clicking the Top Button will start eye setup, making it easy to get into without having to dive into Settings. I do wish they’d allow me to save two eye setups (a feature they already provide for users who may use Apple Vision Pro both with and without ZEISS Optical Inserts), but re-running setup isn’t a huge pain.</p>\n<p>Interestingly, Optic ID works with either configuration without need for re-registration, which was a relief for me, as it means things protected by the secure enclave (including my Persona) don’t need to be reset.</p>\n<h3>Hands</h3>\n<p>Eye tracking is good and all<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn3\" id=\"fnref3\">[3]</a></sup>, but you generally only use your eyes as a pointer, with your hands acting as the “mouse button”. And I’m glad to say I’ve had effectively no issues there, save for having to remember not to accidentally have my finger and thumb touching and to have my hands in view of the cameras.</p>\n<p>It’s also worth noting that, for almost all visionOS UI, you can also directly touch any UI element if it’s close enough to you. This often isn’t practical, as most windows will be further than arm’s reach away, but it can be handy for a webpage or iPad app with odd tap targets. Most users will probably use this most often with the virtual keyboard, but it’s always worth keeping in back of mind in case something can’t be reached with the typical look-and-tap approach.</p>\n<h3>Siri and Dictation</h3>\n<p>Siri has nearly all of the same abilities it has on iPhone and iPad, but it is extra useful in Apple Vision Pro to control windows. You can tell Siri to open or close an app, or even to get rid of everything in your view. This is extremely convenient, and really helps when navigating the system. I will frequently ask Siri to open something like 1Password or Safari if I need to check something quickly while in the context of another app.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/siri.png\" alt=\"A screenshot of Siri in visionOS. A popup displays above a Siri orb with text &quot;A clown held the door for me today. That was a nice jester.&quot; A button shows below the text &quot;Tell me another&quot;\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Additionally, dictation is built deep into the OS. Nearly every search field has a microphone which, if you stare at it long enough, will let you speak your query into it. This is a pretty neat idea, but it seemed to sometimes stop listening if I looked away soon after it started listening to me—resulting in the dictation session being cancelled. That may just be some 1.0 bugginess, though.</p>\n<p>But that’s not all! The virtual keyboard, as well as the QuickType bar that hovers above physically connected keyboards, also has a dictation button, and that one acts as a toggle. Tap it once to start dictation, and again to stop. This works great, and exactly how you would expect it to.</p>\n<h2>Alternative Inputs</h2>\n<p>So that’s what you have out of the box, but Apple provides many alternative ways to interact with the system as well. Not all of these are specifically targeted towards aiding those with vision disabilities, but they all felt worth investigating to compare the benefits and drawbacks of each.</p>\n<h3>Keyboard and Trackpad</h3>\n<p>This isn’t even marketed as an accessibility feature. Connecting a Bluetooth keyboard and/or Magic Trackpad allows you to control apps just as you would an iPad.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/keyboard-trackpad.jpg\" alt=\"Photo of a white Magic Keyboard and space gray Magic Trackpad sit on a kitchen counter in front of a bowl of Life Savers and a HomePod and next to an Aqara G3 camera.\" :=\"\" class=\"img-thumbnail img-thumbnail-smaller mx-auto d-block\"></p>\n<h6>I’ve been keeping a keyboard and trackpad on my kitchen counter for use with my Apple Vision Pro. It’s a bit odd, but I don’t really have any better place to put them when not in use.</h6>\n<p>I find a physical keyboard <em>critical</em> for writing anything longer than a quick search, password, or web address. A trackpad is probably less important, but is still nice to have when managing text selections or interacting with iPad apps that don’t scale as well. It does completely mitigate my eye tracking issues, since I can directly select a control with my trackpad if needed. But, of course, using either a keyboard or trackpad requires you have the desk space to accommodate these peripherals, which may not be true if using the device in a more relaxing environment like lying on the sofa.</p>\n<p>Everything you know about how keyboards and trackpads work on iPad transfers to Apple Vision Pro. You can hold Command to get an overlay of keyboard shortcuts. You can use Command-Space to open Spotlight (which is also accessible via Control Center). The trackpad cursor will jump from element to element (though less often than on iPad, as elements tend to be larger). It all just works.</p>\n<p>The downside, of course, is that you now have to deal with a separate keyboard and trackpad. Do you keep them on a spare desk? Do you pack them with you when you take Apple Vision Pro on the go? It’s a hassle.</p>\n<h3>Pointer Control</h3>\n<p>When your physical trackpad isn’t nearby, you can use Pointer Control. Pointer Control allows you to control a virtual cursor, much like the one you can control with a trackpad, using different parts of your body.</p>\n<ul>\n<li>Eyes: This puts a cursor where you’re looking. It is not at all useful to me, and definitely shows the jitter that the eye tracking sees in my eyes more directly. I could absolutely see it being useful for those with different accessibility needs though.</li>\n<li>Head: This puts a head-locked cursor in the center of your view. This is easily the most stable of all of these options, but is more fatiguing as you have to move your head to select anything.</li>\n<li>Index Finger: This puts a cursor where your finger is pointing. It’s very fast, and feels pretty sci-fi, but falls down a bit in usability for me. Since you’re using your finger to point, it isn’t practical to use the same hand to also select an item, so you have to coordinate between both hands.</li>\n<li>Wrist: This puts a cursor where your wrist is pointing, and feels like the sweet spot between head and index finger to me. It’s not quite as stable as head tracking, but not nearly as fatiguing. It’s not quite as fast as index finger tracking, but lets you use the same hand to select an item as you use to point. Interestingly: Freeform uses a very similar gesture to wrist-based Pointer Control for drawing on the canvas</li>\n</ul>\n<p>With both of the hand-related options, you can also add a ray going from your hand to the cursor, which can aid in keeping track of where the cursor is.</p>\n<div class=\"ratio ratio-16x9\">\n  <iframe src=\"https://www.youtube-nocookie.com/embed/EqjKWUvWrdw?controls=0\" title=\"A video showing Pointer Control using wrist movement on visionOS. A hand moves around a pointer in the Music app. Tapping on Classic Rock, scrolling through featured playlists, then aps the Rock Classics playlist. Looking through the tracks, the user selects Start Me Up by The Rolling Stones. They then open the miniplayer using the button on the bottom toolbar, pause the song, and open the context menu. They then select to view the artist page for The Rolling Stones.\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; muted; picture-in-picture\" allowfullscreen=\"\"></iframe>\n</div>\n<p>None of these options are as “magical” or quick as using eye tracking, but they can be handy to toggle on if I find myself in an app with a harder-to-use interface. Ultimately, Pointer Control is probably the most useful accessibility feature for those with nystagmus.</p>\n<h3>VoiceOver</h3>\n<p>A screen reader in a device designed around vision? You bet. VoiceOver reads content and information about UI elements aloud, relying on many different hand gestures to perform different actions without needing to see the displays<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn4\" id=\"fnref4\">[4]</a></sup>. You can tap your right index finger to move to the next element, your right middle finger to go back to the previous element, and your left index finger to select the highlighted element. You can also hold fingers on one hand while tapping with the other to switch apps or use the Rotor (a concept borrowed from iOS allowing you to quickly change the way VoiceOver steps through elements). Additionally, you can enable an “exploration mode”, allowing you to move your head to find different visible UI elements. VoiceOver also uses spatial audio to help you locate where the element or app that is speaking is in your physical space.</p>\n<div class=\"ratio ratio-16x9\">\n  <iframe src=\"https://www.youtube-nocookie.com/embed/UQiL3Tm6qOA\" title=\"A video showing VoiceOver navigating through the Settings app on visionOS. The user navigates the sidebar to the Accessibility settings, navigates to the Display and Text Size view, toggles on and off Increase Contrast, then goes into Color Filters to toggle them on and off. The user then uses exploration mode to find another option in the sidebar, selecting Eyes and Hands.\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; muted; picture-in-picture\" allowfullscreen=\"\"></iframe>\n</div>\n<h6>Make sure your sound is on for this video.</h6>\n<p>While I think it’s <em>extremely</em> impressive that they were able to ship VoiceOver on such a different product with zero physical input devices, I feel it may be a bit fiddly, at least for a newcomer. When I tried it, I found myself getting a bit lost. I was often using the wrong gesture and going somewhere I didn’t intend. This was particularly notable when multiple apps were open. Trying to figure out what I was controlling and why focus was moving around between apps felt pretty confusing at times. It certainly feels very powerful, however, and I imagine somebody, given enough time to learn how to best utilize it (and maybe a few software updates on Apple’s end to smooth out some of the rough edges) could make great use of VoiceOver in their workflows.</p>\n<p>I don’t personally use VoiceOver much at all on any platform, as my vision isn’t poor enough to require it, but I try to stay proficient at navigating the system with it. It completely changes the way you interact with your device, whether that be a Mac, iPhone, or Apple Vision Pro, and using VoiceOver helps me to think about app design in a more inclusive way.</p>\n<p>One potentially incredible feature of VoiceOver that I couldn’t seem to get working is “describe passthrough” which, if Apple’s descriptions are to be believed, will describe with spoken words what you are looking at in the real world. You can do this on iPhone in the Magnifier app, but having that ability in a head-mounted display would be truly next level. I hope this feature gets working in future software releases.</p>\n<h3>Voice Control</h3>\n<p>Much like on iPhone, iPad, and Mac, Voice Control allows you to select effectively any UI element with your voice. And it works great on Apple Vision Pro too. You can call out “tap Search” or “close window” and it will promptly do so. You can also tell it to “show names” of selectable elements so you know what you can tell it to tap, “show grid” to tap a specific region with accuracy, or “show numbers” to see everything selectable even if they don’t have good accessibility identifiers. This is an incredibly well-built system that, while built for those with mobility issues, can also be useful if eye tracking isn’t working as well, as you can simply tell it to tap the button you intend to select<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn5\" id=\"fnref5\">[5]</a></sup>.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/show-names.png\" alt=\"A screenshot of Apple Music after having said &quot;Show Names&quot;. Popups appear on many UI elements calling out how you can refer to them by name.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/show-numbers.png\" alt=\"A screenshot of Apple Music after having said &quot;Show Numbers&quot;. Popups appear on many UI elements calling out how you can refer to them by number, each having a unique number.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/show-grid.png\" alt=\"A screenshot of Apple Music after having said &quot;Show Grid&quot;. A 2 by 4 grid of numbered squares is shown atop the main window, allowing you to refer to any section of the grid in your next command.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>I don’t use Voice Control all that often, but it can be a nice alternative in some situations, particularly when I’m in a dim environment where the cameras have more trouble tracking my hand movements.</p>\n<h3>Sound Actions</h3>\n<p>This is a fun one. You can assign various actions to different sounds you make. You can open Control Center by making a “P-“ sound, or the Home View by making a “click”. This is a clever idea, but not one that I could seemingly get working. It couldn’t seem to detect the sounds I was making.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/sound-actions.png\" alt=\"A screenshot of the sound actions settings. A practice button is available at the top. Below it are a list of many different sounds and their current assigned actions. Click has none. Cluck has Siri. E-sound has Control Center. Eh has none. K-sound has VoiceOver. La has none. More sounds are available to assign actions to below those visible in the scrollable list.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>Accessibility Shortcut</h3>\n<p>Many of these input options—as well as many output options I discuss later on—have situational use for me. Useful to get at a particularly tricky to tap button or switch, for example. This is where the Accessibility Shortcut comes in handy. Much like on iPhone with a triple tap of the side button or a Mac with a triple press of the Touch ID/power button, triple pressing on the Digital Crown can either toggle a single accessibility setting or, if multiple are selected, present a list of settings to toggle on or off.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/accessibility-shortcut.png\" alt=\"Screenshot of the accessibility shortcut menu on visionOS. A window floats in front of the Settings app. It has a close button in the top left corner, and has a list of options to select. Available options in the list include Increase Contrast, Pointer Control, VoiceOver, and Zoom.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>visionOS allows you to maneuver this list by turning the Digital Crown, making it usable even when your current input method is failing for you.</p>\n<h1>Output</h1>\n<p>Input is only half of the story. Just as important is making sure I can actually make out everything that is being displayed around me.</p>\n<h3>Windows</h3>\n<p>There are two kinds of apps you’ll encounter on Apple Vision Pro: compatible iPhone or iPad apps and native visionOS apps.</p>\n<p>Compatible apps, which live in a special folder in the Home View apart from native apps, use their original iOS light mode visual style. Because these apps haven’t been updated specifically for Apple Vision Pro, their touch targets often feel a bit small, and may not have any highlights to track where you’re looking.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/apple-podcasts.png\" alt=\"A screenshot of Apple Podcasts, a compatible iPad app, running on visionOS. Black text shows on a opaque white background. All controls are confined to a single rectangle. A sidebar shows available views while the content shows in the right 2/3 of the window. A toolbar shows at the bottom of the window with player controls.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Apple Podcasts is a compatible iPad app</h6>\n<p>By contrast, native visionOS apps have an all new visual style. They generally have a translucent “glass” background with light text, keep toolbars in separate floating “ornament” views across the top or bottom, put their tabs into a strip along the side, and have broader spacing between UI elements. These apps not only look fantastic, but also <em>feel</em> so much better than compatible iPad apps with their larger tap targets.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/apple-music.png\" alt=\"A screenshot of Apple Music, a native visionOS app, running on visionOS. White text shows on a dark translucent background. A vertical floating strip shows available views in a compact way on the left while the content shows in a large rectangular window. A toolbar floats in front of the window at the bottom with player controls.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Apple Music is a native visionOS app</h6>\n<p>On my other devices, I almost always use a “dark mode” when available, putting light text on a dark background. But on visionOS, regardless of app type you’re using though, there is no concept of light or dark mode. This means that you’ll forever be using iOS apps with a bright white background &amp; dark text and visionOS apps with a darker translucent background &amp; light text. Going between these feels quite jarring to me.</p>\n<p>I suppose I can understand why, if they are only going to pick one style for compatible apps, Apple would chose light mode as the only visual style. Even as a dark mode user, I still think of light mode as the “canonical” visual style on iOS. But in use it just feels out ofqqq place to me. This was made even clearer to me when I turned on the manual overrides available in apps like Overcast, Ivory, and Discord to use their dark modes anyway. This made those apps <em>immediately</em> feel better to use alongside native apps for me. I really hope Apple reconsiders this design decision in the future.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/overcast.png\" alt=\"A screenshot of Overcast, a compatible iPad app, running on visionOS with its dark mode enabled in the app settings. White text appears on a pure black background.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Overcast in dark mode. So much nicer.</h6>\n<h2>Output Accommodations</h2>\n<p>Beyond these basics, visionOS also provides many options to adapt its interface to be easier to view, no doubt stemming from its lineage to iOS and iPadOS which have had stellar support in this area for years.</p>\n<h3>Text Size</h3>\n<p>Just like on iPhone and iPad, you can set your system-wide text size as big as you want, or even smaller than default if you prefer. This allows me to make sure everything is readable without needing to get as close to windows, which is particularly important in a headset where the focal plane is far enough out that getting too close to virtual objects makes them blurry<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn6\" id=\"fnref6\">[6]</a></sup>.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/text-size.png\" alt=\"Comparison of two screenshots of the Music app. In both, the Album page for Born This Way by Lady Gaga is seen. The top screenshot shows it with a 100% text size, while the bottom shows it with 150% text size.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>100% vs 150% text size.</h6>\n<p>visionOS text size stops are at different scales than iOS’ sizes. The steps on iOS from 100% to 150% are 100-110-120-135-150%, while visionOS has steps at 100-105-115-125-130-150%. The largest accessibility text size on Apple Vision Pro is only 200%, while iPhone scales all the way to 310%. I also personally use a higher absolute text size option in Apple Vision Pro then I do my iPhone (Either 130% or 150% on Apple Vision Pro vs 120% on iPhone). Given both of these, I would definitely be wary of recommending this product to anyone who uses one of the larger accessibility text sizes on iPhone or iPad.</p>\n<p>In many apps, especially those written with modern APIs like SwiftUI, text size scaling isn’t much of a concern at all. But text being cut off or mismatched in size can definitely be an issue in some apps. This is nothing new, as you can see many of these same issues on iOS with high enough text sizes, but is worth keeping in mind if you’re planning on using a larger text size. I notice these issues in Apple Vision Pro a lot more when using compatible iPad apps.</p>\n<p>You can also set text size per app, but, unlike iOS, you can only do this deep within the accessibility section of Settings, not with the Text Size control in Control Center. I would love to see this flow be as easy as on iPhone in the future (FB13629357).</p>\n<h3>Window Zoom</h3>\n<p>On iPhone and iPad, you can change the UI scale of all elements of your device’s display using “Display Zoom”. Window Zoom is the same concept, but, due to the nature of the device, only scales digital windows—<em>not</em> your physical space. You can select between four sizes, from small to extra large. I use the extra large size which, while it may mean I can fit fewer windows in my view, makes them much more visible to me.</p>\n<p><video loop=\"\" autoplay=\"\" muted=\"\" controls=\"\" playsinline=\"\" preload=\"auto\" class=\"img-fluid\" src=\"https://zmknox.com/resources/vision-accessibility/window-zoom.mp4\" title=\"Looping video of 4 images showing the Window Zoom settings in each window scale\"></video></p>\n<p>I find this setting to be much more useful than the equivalent on iPhone and iPad, where I prefer to only change the text size. I think this mainly comes down to how windows <em>feel</em>. A visionOS window is sort of similar to a whole display in physical space. And, much like how I have a 65-inch TV in a space where many other folks may have only gotten a 55-inch, the extra size helps.</p>\n<p>You can also enable a setting to allow you to individually scale any window using a two-handed pinch. This feels <em>incredibly</em> sci-fi, and can be helpful if something is just a bit too small for you.</p>\n<p><video loop=\"\" autoplay=\"\" muted=\"\" controls=\"\" playsinline=\"\" preload=\"auto\" class=\"img-fluid\" src=\"https://zmknox.com/resources/vision-accessibility/window-scaling.mov\" title=\"Video of the Window Zoom settings, showing window scaling with a two-handed pinch\"></video></p>\n<h3>Increase Contrast</h3>\n<p>To aid in content readability, this setting makes the glass background of visionOS windows darker and more opaque.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/increase-contrast.jpg\" alt=\"A comparison photo of the Display and Text Size settings with Increase Contrast enabled and disabled\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>For me, this doesn’t <em>usually</em> make a huge difference, but can be helpful for dark colored text on some backgrounds (particularly red destructive actions in context menus). I tend to leave this off to keep a standard visionOS aesthetic, but I keep it in my Accessibility Shortcut just in case.</p>\n<h3>Increase Focus State</h3>\n<p>Much like on tvOS, it can sometimes be a bit hard to tell where your focus is. To accommodate this, you can enable a setting to add a more prominent highlight around the currently focused UI element. This doesn’t show for <em>every</em> UI element in visionOS, but it shows on most buttons, tables, and other controls.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/increase-focus-state.jpg\" alt=\"Comparison of two screenshots of the Display and Text Size accessibility settings on visionOS. One has Increase Focus State disabled, while the other has it enabled. Focus is on the &quot;Eyes and Hands&quot; option in the left sidebar. The version with the setting enabled has a white outline around the option, while the version without only highlights the background.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>I’m looking at the “Eyes and Hands” option in the left sidebar</h6>\n<p>This option does have the side effect of making some parts of the interface a fair bit uglier. The virtual keyboard, for example, does not play nice with the focus state highlight at all.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/bad-outline.png\" alt=\"Screenshot of the virtual keyboard with focus on the backspace key. The white selection outline gets cut off on the corners of the button.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>See the bad outline on the backspace key</h6>\n<h3>Zoom</h3>\n<p>Another classic Mac and iPhone accessibility setting reimagined. Zoom gives you a virtual head-locked magnifying glass<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn7\" id=\"fnref7\">[7]</a></sup> to view digital content through. You can scale the zoom level using the Digital Crown, or move the window around in your view. This can be useful to view or interact with very small UI elements, particularly in compatible iPad apps.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/zoom.png\" alt=\"Screenshot of the Zoom settings with zoom enabled, with the zoom window focused on a toggle switch\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>I don’t find myself using zoom on Apple Vision Pro nearly as much as I do on my iPhone or iPad, as there are more opportunities to make the windows themselves larger instead.</p>\n<p>One interesting quirk of Zoom is that it <em>only</em> zooms digital content, <em>not</em> passthrough. So you can’t use it as a physical magnifying glass. This feels really strange at first, since the digital windows are otherwise anchored incredibly well in your real world space.</p>\n<h3>View Mirroring</h3>\n<p>This is much less relevant for most use of this device, but is still an important output. Apple Vision Pro can mirror a view of what the wearer is seeing to any compatible AirPlay receiver, iPhone, or iPad. This allows you to show others what you’re experiencing. And, while not designed as an accessibility feature, mirroring your view could be helpful if you, as the wearer, need assistance in dealing with the digital content in front of you.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/view-mirroring.png\" alt=\"A screenshot of the View Mirroring menu within Control Center on visionOS. A window with &quot;Mirror My View&quot; heading has a list of options including an Apple TV named &quot;Living Room&quot;, a TV, an iPhone named &quot;zmk Fifteen Pro&quot;, and a Mac named &quot;zmk MacBook Pro 16&quot;. Text at the bottom of the window reads &quot;Select a device to mirror content to from your Apple Vision Pro&quot;.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>Display Accommodations</h3>\n<p>Beyond all these options, many of the same settings from iOS remain on visionOS. You can add a color filter to both digital content and passthrough. You can invert colors on all digital content. And you can ask the system to reduce motion. All in all, a very complete accessibility package.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/classic-invert.png\" alt=\"A screenshot of the Settings app on visionOS with Classic Invert enabled. The colors of all UI within the app window are inverted, resulting in dark text on a light translucent background. The passthrough video surrounding the window is not inverted.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Classic Invert enabled</h6>\n<h1>Does It Work For Me?</h1>\n<p>I wrote much of this piece while in Joshua Tree using the fabulous <a href=\"https://apps.apple.com/us/app/runestone-text-editor/id1548193893\">Runestone</a> text editor, using a connected Magic Keyboard and referring to my notes previously made on my iPhone in Apple Notes. I had the Apple Music miniplayer to my right, Discord to my left<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn8\" id=\"fnref8\">[8]</a></sup>, and a <a href=\"https://apps.apple.com/us/app/widgetsmith/id1523682319\">Widgetsmith</a> clock floating up in the sky behind Notes. I was skeptical that I could actually use this device for productive tasks, but after this experience I know I <em>absolutely</em> could. I don’t know if I want to do my 40-hour-a-week software development job while wearing such a heavy thing all day, but it’s fantastic for tasks like writing a blog post.</p>\n<p><img src=\"https://zmknox.com/resources/vision-accessibility/behind-the-curtain.png\" alt=\"Screenshot of windows in visionOS in the Joshua Tree environment. Notes appears on the left with bullets relating to this article. A clock widget shows above it reading 6:56pm. To the right of Notes is a text editor window with a rough draft of this piece. Below is is a hint of passthrough showing a Magic Keyboard. In the top right, the Apple Music miniplayer can be partially seen with the album art for Neil Cicierega's Mouth Moods displayed.\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Is it perfect? Certainly not. I hit a lot more rough spots than many other users would. But it didn’t catastrophically break down for me either. And compared to the competition, Apple Vision Pro is <em>vastly</em> more accessible. On Meta Quest, you can change the text size of system apps, but that’s about it. Many games and apps on that platform simply don’t accommodate those who may need larger text or views, and that system doesn’t provide any accommodations to zoom or scale third-party content. Apple Vision Pro not only provides accessibility settings that can affect the content of third-party apps, but also offers additional options to allow users to interact with apps that are still tricky to use despite those accommodations. I’m incredibly impressed with the amount of accessibility functionality they shipped on <em>day one</em>.</p>\n<p>So I didn’t return it. I’m very excited to see how this brand new platform<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fn9\" id=\"fnref9\">[9]</a></sup> will evolve over the coming months and years.</p>\n<hr>\n<p>Before publishing, I came across <a href=\"https://mastodon.social/@pesh/111950125924980596\">this short Mastodon thread from Peter Saathoff-Harshfield</a> describing their own experiences with trying the accessibility features with Apple Vision Pro during an in-store demo which, as they have very different vision disabilities than me, were different from mine. I recommend you check it out if you want another perspective on visionOS accessibility.</p>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>If you’re curious about the history of Apple’s early accessibility efforts, I highly recommend listening to Shelly Brisbin’s <a href=\"http://www.36seconds.org/\">36 Seconds That Changed Everything</a> documentary about that very topic. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>My contacts also have a not-super-strong prescription, but I don’t use any optical inserts, as I already go without my contacts many days, mainly when at home, anyway. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>You can actually control Apple Vision Pro with <em>only</em> your eyes if needed using Dwell Control (an accessibility feature I’m not covering here as it, by design, assumes you have good vision) <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p>VoiceOver really falls into both the input and output sections, as it radically changes both. I chose to put it with the “inputs” as that is where VoiceOver felt most different to its counterpart on iOS for me. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p>I also find Voice Control useful when I’m under the covers of a warm blanket and don’t want to take my arm out to tap something on my iPad. Simply tell Siri to turn voice control on and then tell your device what to tap! <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p>I find Apple Vision Pro to be better in this regard than Meta Quest 3, allowing you to get a bit closer without issues. But it’s definitely still noticeable. I find that visionOS elements start to get a bit blurry closer than about an arm’s length away from me, though don’t get unusably blurry until you’re a fair bit closer. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p>You can also zoom your entire view of digital content, but I find this fairly disorienting. Making the window the default was the right call. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn8\" class=\"footnote-item\"><p>The Discord iPad app on Apple Vision Pro is Not Great. It doesn’t do much at all for highlighting where you’re looking to select, and has issues with the media picker. But I’m still glad it exists so I can keep up with things while wearing the device. <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref8\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn9\" class=\"footnote-item\"><p>I <em>of course</em> followed <a href=\"https://developer.apple.com/visionos/submit/\">Apple’s Developer guidelines</a> on how to refer to Apple Vision Pro in this piece 🙃 <a href=\"https://zmknox.com/2024/02/vision-accessibility-on-apple-vision-pro/#fnref9\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2024-02-18T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2023/12/app-defaults-2024/",
      "url": "https://zmknox.com/2023/12/app-defaults-2024/",
      "title": "My App Defaults for 2024",
      "content_html": "<p>It’s time for a new year and so, inspired by an <a href=\"https://defaults.rknight.me\">initiative spawned by the Hemipsheric Views podcast and further expanded by Robb Knight</a> for folks to share their default apps, I thought I’d share a default app list of my own! This is primarily focused on stuff I use outside of my actual job, which also means my list is extremely iPhone and iPad focused. I have a personal Mac, but I use it shockingly little as of late.</p>\n<!-- more -->\n<p>📨 Mail Client</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/gmail-email-by-google/id422689480?itsct=apps_box_link&amp;itscg=30200\">Gmail</a> and <a href=\"https://apps.apple.com/us/app/fastmail-email-calendar/id931370077?itsct=apps_box_link&amp;itscg=30200\">Fastmail</a> for iOS. I don’t particularly love them, but they do the job.</li>\n<li>RIP <a href=\"https://www.theverge.com/2015/12/8/9873268/why-dropbox-mailbox-shutdown\">Mailbox</a>, the only good mobile email app</li>\n</ul>\n<p>📮 Mail Server</p>\n<ul>\n<li><a href=\"https://mail.google.com/\">Gmail</a> for personal. Because of course it is. I certainly don’t use Yahoo! mail.</li>\n<li><a href=\"https://www.fastmail.com\">Fastmail</a> for domain mail. Particularly good is you have a bunch of domains you want to manage on one account.</li>\n</ul>\n<p>📝 Notes</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/notes/id1110145109?itsct=apps_box_link&amp;itscg=30200\">Apple Notes</a>. I find the system app to be sufficient for the vast majority of my quick note-taking needs.</li>\n<li><a href=\"https://apps.apple.com/us/app/day-one-journal-private-diary/id1044867788?itsct=apps_box_link&amp;itscg=30200\">Day One</a> for journaling. I highly recommend everyone try journaling, at least for a bit. It may not be for you, but I find that getting whatever thoughts I have out of my head to be immensely valuable.</li>\n</ul>\n<p>✅ To-Do</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/reminders/id1108187841?itsct=apps_box_link&amp;itscg=30200\">Reminders</a>. I have no formal task management system. It mostly boils down to “<em>Siri, remind me to do laundry tomorrow at 11 AM</em>”</li>\n</ul>\n<p>📷 iPhone Photo Shooting</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/camera/id1584216193?itsct=apps_box_link&amp;itscg=30200\">Camera</a>. I’ve tried third party camera apps and, while they’re great, they never end up sticking for me.</li>\n</ul>\n<p>🟦 Photo Management</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/photos/id1584215428?itsct=apps_box_link&amp;itscg=30200\">iCloud Photos</a>. I live in the ecosystem, and so iCloud is all I really need for photo management.</li>\n</ul>\n<p>🎞️ Image and Vector Editors</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/pixelmator-pro/id1289583905?mt=12&amp;itsct=apps_box_link&amp;itscg=30200\">Pixelmator Pro</a>. Truly one of my favorite apps and developers of all time. They make a stellar image editor and have been continually improving it for over a decade.</li>\n<li><a href=\"https://apps.apple.com/us/app/pixelmator/id924695435?itsct=apps_box_link&amp;itscg=30200\">Pixelmator for iOS</a>. You’d think it would be hard to do complex image edits on a phone, but I’ve done this time and time again. If I have an idea for some silly meme, I can make it on my phone right then and there.</li>\n<li><a href=\"https://apps.apple.com/us/app/affinity-designer-2/id1616831348?mt=12&amp;itsct=apps_box_link&amp;itscg=30200\">Affinity Designer 2</a>. I’m not great at making vector graphics, but the Affinity suite is still a really great tool whenever I need to make some.</li>\n</ul>\n<p>🎬 Video Editor</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/final-cut-pro/id424389933?mt=12&amp;itsct=apps_box_link&amp;itscg=30200\">Final Cut Pro</a> for Mac. I’m far from a professional, but I always find a great deal of joy whenever I open Final Cut. It makes editing fun for me.</li>\n<li><a href=\"https://apps.apple.com/us/app/lumafusion/id1062022008?itsct=apps_box_link&amp;itscg=30200\">LumaFusion</a> for iPad and iPhone. This serves a similar purpose to Pixelmator for iOS. Good for making quick meme videos. I’m not at all competent at using it well, but it’s a good tool to have if I need it.</li>\n</ul>\n<p>🎧 Audio Editor</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/logic-pro/id634148309?mt=12&amp;itsct=apps_box_link&amp;itscg=30200\">Logic Pro</a> for Mac. Logic is super powerful, and I barely know how to use it. But it’s got everything I need if I want to play around with music, edit podcast-style stuff, or just clip something out. Not as fun as Final Cut though.</li>\n<li><a href=\"https://apps.apple.com/us/app/ferrite-recording-studio/id1018780185?itsct=apps_box_link&amp;itscg=30200\">Ferrite</a> for iPad. It’s incredible what you can do with this app. It makes it easy to work on podcast-style audio with the same speed I might in Logic, but on a much more comfortable device.</li>\n</ul>\n<p>📆 Calendar</p>\n<ul>\n<li><a href=\"https://calendar.google.com/\">Google Calendar</a> via <a href=\"https://apps.apple.com/us/app/calendar/id1108185179?itsct=apps_box_link&amp;itscg=30200\">Apple Calendar</a> app. Nothing special.</li>\n</ul>\n<p>📁 Cloud File Storage</p>\n<ul>\n<li><a href=\"https://www.icloud.com/iclouddrive\">iCloud Drive</a>. I don’t rely that much on cloud storage beyond basic documents, where iCloud Drive is sufficient</li>\n<li><a href=\"https://drive.google.com/\">Google Drive</a> for sharing files, because iCloud is still not quite sufficient in that area…</li>\n</ul>\n<p>📖 RSS</p>\n<ul>\n<li>Not a frequent user, but <a href=\"https://apps.apple.com/us/app/netnewswire-rss-reader/id1480640210?itsct=apps_box_link&amp;itscg=30200\">NetNewsWire</a> when doing so.</li>\n</ul>\n<p>🙍🏻‍♂️ Contacts</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/contacts/id1069512615?itsct=apps_box_link&amp;itscg=30200\">iCloud Contacts</a>. Why are these in iCloud but my calendar is in Google? I don’t really know.</li>\n</ul>\n<p>🌐 Browser</p>\n<ul>\n<li><a href=\"https://arc.net\">Arc</a> on Mac. I love love love some of its UI innovations. The sidebar and Mini Arc are truly great features.</li>\n<li><a href=\"https://apps.apple.com/us/app/safari/id1146562112?itsct=apps_box_link&amp;itscg=30200\">Safari</a> on iPhone and iPad. Safari is the only browser on iOS that supports extensions, so I wouldn’t even consider switching.</li>\n</ul>\n<p>💬 Chat</p>\n<p>I will basically use whatever app I need to to reach folks</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/messages/id1146560473?itsct=apps_box_link&amp;itscg=30200\">iMessage</a></li>\n<li><a href=\"https://discord.com\">Discord</a></li>\n<li><a href=\"https://slack.com\">Slack</a></li>\n<li><a href=\"https://www.signal.org\">Signal</a></li>\n<li><a href=\"https://www.messenger.com\">Messenger</a></li>\n<li>I have others but don’t really use them…</li>\n</ul>\n<p>🗣️ Social apps</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/ivory-for-mastodon-by-tapbots/id6444602274?itsct=apps_box_link&amp;itscg=30200\">Ivory</a> for Mastodon, connecting to the very nice <a href=\"https://snailedit.social/about\">snailedit.social</a></li>\n<li><a href=\"https://www.threads.net\">Threads</a>, for the people who aren’t on Mastodon</li>\n</ul>\n<p>🔖 Bookmarks</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/safari/id1146562112?itsct=apps_box_link&amp;itscg=30200\">Safari</a>. But tbh I basically never make bookmarks.</li>\n</ul>\n<p>📑 Read It Later</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/goodlinks/id1474335294?itsct=apps_box_link&amp;itscg=30200\">GoodLinks</a>. A really nice app with a really easy to use extension that I don’t use nearly enough. I add a bunch of stuff there, sure. But I don’t often go to actually read it.</li>\n</ul>\n<p>📜 Word Processing</p>\n<p>Equal opportunity word processing!</p>\n<ul>\n<li><a href=\"https://docs.google.com/\">Google Docs</a> for collaboration</li>\n<li><a href=\"https://apps.apple.com/us/app/pages/id361309726?itsct=apps_box_link&amp;itscg=30200\">Pages</a> for personal things</li>\n<li><a href=\"https://www.microsoft.com/en-us/microsoft-365/word\">Microsoft Word</a> for more formal things</li>\n</ul>\n<p>📈 Spreadsheets</p>\n<ul>\n<li><a href=\"https://sheets.google.com/\">Google Sheets</a>. Numbers confuses me and Excel sheets are harder to live collaborate on, so Google Sheets it is.</li>\n</ul>\n<p>📊 Presentations</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/keynote/id409183694?mt=12&amp;itsct=apps_box_link&amp;itscg=30200\">Keynote</a>. There really is no comparison here. Keynote is ahead of the other presentation apps by a mile. It’s another one of those apps that feels fun to use.</li>\n</ul>\n<p>🎵 Music</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/apple-music/id1108187390?itsct=apps_box_link&amp;itscg=30200\">Apple Music</a>. I like the library-focused approach Apple takes, as I’d built up my iTunes library for years before Apple Music launched, and still add in a bunch of stuff that isn’t on Apple Music to my library regularly. I also find their playlists and some of their radio shows to be quite good.</li>\n<li><a href=\"https://www.sonos.com/en-us/sonos-radio\">Sonos Radio</a>. It may have ads, but I find their mixes to be quite solid, and wish they offered more than radio.</li>\n<li>Rarely <a href=\"https://apps.apple.com/us/app/youtube-music/id1017492454?itsct=apps_box_link&amp;itscg=30200\">YouTube Music</a> if I’m looking for something not on Apple Music</li>\n</ul>\n<p>🎤 Podcasts</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/overcast/id888422857?itsct=apps_box_link&amp;itscg=30200\">Overcast</a>. Which gets <em>a lot</em> of use.</li>\n</ul>\n<p>🔐 Password Management</p>\n<ul>\n<li><a href=\"https://apps.apple.com/us/app/1password-password-manager/id1511601750?itsct=apps_box_link&amp;itscg=30200\">1Password</a>. I don’t despise 1Password 8 like many do, but it can be frustrating at times. In general, though, I still find it to be the tool that fits my needs the best. Except…</li>\n<li><a href=\"https://support.apple.com/guide/iphone/passkeys-passwords-devices-iph82d6721b2/ios\">iCloud Keychain</a> for Passkeys. I find the system integration for passkeys to be smoother, at least for now. Maybe this will change in the future, but I’m split at present.</li>\n</ul>\n<p>📋 Clipboard Manager</p>\n<ul>\n<li><a href=\"https://tapbots.com/pastebot/\">Pastebot</a>. Everybody should use a clipboard manager. They;re truly invaluable tools that can change how you work, I find that Pastebot’s simple overlay and features like sequential paste work well for me.</li>\n</ul>\n<p>🧑‍💻 Code Editor</p>\n<ul>\n<li><a href=\"https://code.visualstudio.com\">VS Code</a>. I’ve tried everything, but always keep coming back to VS Code. Yes, it’s Electron. But it has every feature I need, extensions for any language I will ever use, and works everywhere. As someone who primarily works with JavaScript, it works great for me.</li>\n</ul>\n<p>⌨️ Terminal</p>\n<ul>\n<li><a href=\"https://iterm2.com\">iTerm 2</a>. The built-in macOS terminal is fine, but I appreciate the extra customization options that iTerm provides.</li>\n</ul>\n<p>And that’s my list! I hope you found something interesting in the list. And be sure to check out <a href=\"https://defaults.rknight.me\">Robb’s web page</a> which links to everyone else’s app defaults.</p>\n",
      "date_published": "2023-12-30T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2021/08/building-a-donation-tracker-widget/",
      "url": "https://zmknox.com/2021/08/building-a-donation-tracker-widget/",
      "title": "Building a Donation Tracker Widget with Scriptable",
      "content_html": "<p>Every year, coinciding with <a href=\"https://www.stjude.org/get-involved/other-ways/childhood-cancer-awareness-month.html\">Childhood Cancer Awareness month</a>, the <a href=\"https://relay.fm\">Relay FM</a> podcast network does a “<a href=\"https://youtu.be/UEuktMKaEq0\">Podcastathon</a>” to raise funds for <a href=\"https://stjude.org/\">St. Jude Children’s Research Hospital</a>. (You can donate now at <a href=\"https://stjude.org/relay\">stjude.org/relay</a>!)</p>\n<p>With many donation milestones and incentives along the way to their goal, many community members are stepping up to make donation trackers. My friend Matt VanOrmer made a <a href=\"https://zmknox.com/resources/donation-widget/feedbackbot.jpg\">special command for his FeedbackBot</a> in the Relay FM Members Discord and <a href=\"https://www.peerreviewed.io/blog/2021/8/20/a-home-screen-widget-for-relay-fms-annual-st-jude-fundraiser\">an iOS widget in Python using Pyto</a>. I decided to take on the challenge of making my own version of Matt’s widget using JavaScript with <a href=\"https://scriptable.app/\">Scriptable</a>. Scriptable allows us to build real iOS user interfaces, including widgets, by writing JavaScript directly on device, and seems like the perfect tool for this job.</p>\n<p>Let’s get started.</p>\n<!-- more -->\n<h2>Getting the data</h2>\n<p>Relay is using <a href=\"https://tiltify.com/\">Tiltify</a> to track their donations. Luckily for us, Tiltify has APIs that we can use to access data on campaigns.</p>\n<p>Here’s how we’re making the request with GraphQL:</p>\n<pre><code class=\"language-javascript\">const req = new Request('https://api.tiltify.com');\nreq.headers = {    \n  'Content-Type': 'application/json'\n};\nreq.method = &quot;POST&quot;;\nreq.body = JSON.stringify({\n\t&quot;operationName&quot;: &quot;get_campaign_by_vanity_and_slug&quot;,\n\t&quot;variables&quot;: {&quot;vanity&quot;: &quot;@relay-fm&quot;, &quot;slug&quot;: &quot;relay-st-jude-21&quot;},\n\t&quot;query&quot;: `query get_campaign_by_vanity_and_slug($vanity: String, $slug: String) {\n  campaign(vanity: $vanity, slug: $slug) {\n    id\n    name\n    slug\n    status\n    originalGoal {\n      value\n      currency\n    }\n    team {\n      name\n    }\n    description\n    totalAmountRaised {\n      currency\n      value\n    }\n    goal {\n      currency\n      value\n    }\n    avatar {\n      alt\n      height\n      width\n      src\n    }\n    milestones {\n      id\n      name\n      amount {\n        value\n        currency\n      }\n    }\n  }\n}`\n});\nlet body = await req.loadJSON();\n</code></pre>\n<p>Within our GraphQL query, we’re requesting many fields including the campaign’s name, its goal, how much has been raised so far, and any milestones set along the way to the goal. This API request returns to us a JSON object that mimics what our GraphQL query looks like:</p>\n<pre><code class=\"language-json\">{\n  &quot;data&quot;: {\n    &quot;campaign&quot;: {\n      &quot;avatar&quot;: {\n        . . .\n      },\n      &quot;goal&quot;: {\n        &quot;currency&quot;: &quot;USD&quot;,\n        &quot;value&quot;: &quot;333333.33&quot;\n      },\n      &quot;milestones&quot;: [\n        {\n          &quot;amount&quot;: {\n            &quot;currency&quot;: &quot;USD&quot;,\n            &quot;value&quot;: &quot;20000.00&quot;\n          },\n          &quot;id&quot;: 127775,\n          &quot;name&quot;: &quot;Myke and Stephen attempt Flight Simulator again&quot;\n        },\n        . . .\n      ],\n      &quot;id&quot;: 121745,\n      &quot;slug&quot;: &quot;relay-st-jude-21&quot;,\n      &quot;status&quot;: &quot;published&quot;,\n      &quot;team&quot;: null,\n      &quot;description&quot;: &quot;. . .&quot;,\n      &quot;totalAmountRaised&quot;: {\n        &quot;currency&quot;: &quot;USD&quot;,\n        &quot;value&quot;: &quot;19065.66&quot;\n      },\n      &quot;name&quot;: &quot;Relay FM for St. Jude 2021&quot;,\n      &quot;originalGoal&quot;: {\n        &quot;currency&quot;: &quot;USD&quot;,\n        &quot;value&quot;: &quot;100&quot;\n      }\n    }\n  }\n}\n</code></pre>\n<p>Now, we can use this data to build a widget…</p>\n<h2>Building the widget</h2>\n<p>Scriptable lets us build widgets as a list of elements, declared one after another. Let’s start by creating the <code>ListWidget</code> object and set a gradient. Scriptable’s <code>LinearGradient</code> class allows us to build a more vibrant background. Here, I’m making a gradient of two colors (each set by their RGB hex code), and setting its location to run top to bottom:</p>\n<pre><code class=\"language-javascript\">const primaryTextColor = new Color(&quot;#efefef&quot;);\n\nconst widget = new ListWidget();\nwidget.setPadding(8, 15, 8, 10);\n\n// widget background\nconst gradient = new LinearGradient();\ngradient.colors = [    \n    new Color(`#dcb748`),\n    new Color(`#d6aa29`)\n];\ngradient.locations = [0.0, 1];\nwidget.backgroundGradient = gradient;\n\nwidget.url = &quot;https://stjude.org/relay&quot;;\n</code></pre>\n<p>Now we can start adding elements to our widget. Let’s add a big title using the name we got from Tiltify. We can set its text as well as a few variables like color and font style. Additionally, let’s add a spacer element after the text to keep our widget looking nice:</p>\n<pre><code class=\"language-javascript\">const titleText = widget.addText(body.data.campaign.name);\ntitleText.textColor = primaryTextColor; // primaryTextColor set in earlier code block\ntitleText.font = Font.boldSystemFont(24);\n\nwidget.addSpacer(8);\n</code></pre>\n<p>And finally we get to the good stuff: the actual donation total! Lets add a text element which shows both the total so far as well as the current campaign goal. Tiltify gives them to us as ready-to-go strings, but I’ve converted them to numbers here and back using toLocaleString() to get proper comma separated number formatting:</p>\n<pre><code class=\"language-javascript\">const soFar = parseFloat(body.data.campaign.totalAmountRaised.value);\nconst total = parseFloat(body.data.campaign.goal.value);\n\nconst amountText = widget.addText(`$${soFar.toLocaleString()} / $${total.toLocaleString()}`);\namountText.textColor = primaryTextColor;\namountText.font = Font.heavyRoundedSystemFont(20);\n\nwidget.addSpacer(6);\n</code></pre>\n<p>But I think we can do better than just text. Why not add a progress bar to visually show the donation total?</p>\n<h2>Adding a Progress Bar</h2>\n<p>To render our progress bar, we need to programmatically create an image to visualize the donation total. Let’s make a function that will handle this creation.</p>\n<p>This function will use a <code>DrawContext</code> to handle the layout of our visual elements. These are done in absolute points, as opposed to stacking vertically like <code>ListWidget</code>. Here, we’re creating two rounded rectangles. The first, <code>bgPath</code>, will fill our entire width and height as specified, with rounding at about half the height. The second, <code>fgPath</code> will only be as wide as the percentage of donations that have been received towards the campaign’s gaol. We’ve placed these bars such that <code>fgPath</code> is drawn in front of <code>bgPath</code>, which will give us a nice looking progress bar:</p>\n<pre><code class=\"language-javascript\">const width = 320;\n\nfunction createProgressBar(total, soFar, height, showPercentage = false) {\n    const context = new DrawContext();\n    context.size = new Size(width, height);\n    context.opaque = false;\n    context.respectScreenScale = true;\n    \n    // bar background\n    context.setFillColor(new Color(&quot;#48484b&quot;));\n    const bgPath = new Path();\n    bgPath.addRoundedRect(new Rect(0, 0, width, height), height / 2, (height / 2) - 1);\n    context.addPath(bgPath);\n    context.fillPath();\n    \n    // bar foreground\n    context.setFillColor(new Color(&quot;#00b100&quot;));\n    const fgPath = new Path();\n    fgPath.addRoundedRect(new Rect(0, 0, (width * soFar)/total, height), height / 2, (height / 2) - 1);\n    context.addPath(fgPath);\n    context.fillPath();\n</code></pre>\n<p>To finish off the progress bar, let’s add a text label to, optionally, show the percentage raised as as string. We can overlay this on top of the progress bar for a cool look. The text drawing functions for <code>DrawContext</code> are similar to what we used when adding text to our <code>ListWidget</code>, but do have some of their own syntax to deal with the positioning.</p>\n<pre><code class=\"language-javascript\">    if (showPercentage) {\n        const percentage = ((soFar / total) * 100).toFixed(2);\n        let xPos = (width * soFar)/total + 5;\n        // if over 70%, show in foreground area\n        // to ensure that it doesn't overflow the display\n        if (percentage &gt; 70) {\n            xPos = (width * soFar)/total - 55;\n        }\n\n        context.setFont(Font.semiboldRoundedSystemFont(14));\n        context.setTextColor(primaryTextColor);\n        context.drawText(`${percentage}%`, new Point(xPos, (height / 14)));\n    }\n\n    return context.getImage();\n}\n</code></pre>\n<p>Finally, lets add our progress bar image to our widget:</p>\n<pre><code class=\"language-javascript\">const progressBar = widget.addImage(createProgressBar(total, soFar, 20, true));\nprogressBar.imageSize = new Size(width, 20);\n\nwidget.addSpacer(4);\n</code></pre>\n<p>With that, you now have a fully functional widget. Let’s take a look:</p>\n<p><img src=\"https://zmknox.com/resources/donation-widget/widget-basic.jpg\" alt=\"A medium and a large widget displaying the current percentage of the Relay FM for St. Jude 2021 fundraising campaign\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>But our large widget is looking a little empty. What if we could add some milestone progress bars to fill that up?</p>\n<h2>Adding Milestones</h2>\n<p>Tiltify gives us the campaign’s milestones as an array, which allows us to create a progress bar for each milestone by simply looping through it. We can also allow the user to choose how many milestones are displayed by using the widget parameter field:</p>\n<pre><code class=\"language-javascript\">// only show as many milestones as the user requests\n// (1 looks good on a medium widget, 3 or 4 on a large)\nlet maxMilestones = args.widgetParameter;\nlet milestonesDisplayed = 0;\n// progress bars for milestones\nfor (let milestone of body.data.campaign.milestones) {\n    if (maxMilestones != undefined &amp;&amp; milestonesDisplayed &gt;= maxMilestones) {\n        break;\n    }\n</code></pre>\n<p>The rest of this loop should look pretty familiar to what we were making before: we’re adding text and progress bars to our widget. Here, though, we’re combining two text elements into a stack, which allows us to place the milestone name side-by-side with its percentage in a different font. We’ve also added a conditional such that we’re only adding milestones with less than 100% progress. This ensures we’re only showing milestones that are relevant to the current total:</p>\n<pre><code class=\"language-javascript\">    // calculate milestone percentage\n    const milestoneTotal = parseFloat(milestone.amount.value);\n    const percentage = (soFar / milestoneTotal) * 100;\n    \n    // if the milestone is long past, no need to show it\n    if (percentage &lt; 110) {    \n        widget.addSpacer(6);\n        \n        // a stack will let us put the name and percentage side by side\n        const stack = widget.addStack();\n        stack.spacing = 4;\n    \n        // milestone name\n        const milestoneNameText = stack.addText(milestone.name);\n        milestoneNameText.textColor = primaryTextColor;\n        milestoneNameText.font = Font.boldSystemFont(16);\n        milestoneNameText.lineLimit = 2;\n        \n        // milestone percentage\n        const percentageFixed = percentage.toFixed(2);\n        const percentageText = stack.addText(`${percentageFixed}%`);\n        percentageText.textColor = primaryTextColor;\n        percentageText.font = Font.regularRoundedSystemFont(16);\n        \n        widget.addSpacer(6);\n    \n        // milestone progress bar\n        const milestoneProgressBar = widget.addImage(createProgressBar(milestoneTotal, soFar, 10, false));    \n        milestoneProgressBar.imageSize = new Size(width, 10);\n        \n        milestonesDisplayed += 1;\n    }\n}\n</code></pre>\n<p>Let’s see how that looks:</p>\n<p><img src=\"https://zmknox.com/resources/donation-widget/widget-full.jpg\" alt=\"A medium and a large widget displaying the current percentage of the Relay FM for St. Jude 2021 fundraising campaign, as well as the percentages to 1 and 3 milestones respectively\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Looking good! I think that’s much nicer than our older large widget.</p>\n<h2>Try It Yourself!</h2>\n<p>You can download this script to use on your own device from <a href=\"https://github.com/zmknox/relay-stjude-scriptable-widget\">my GitHub repository</a>. You’ll need <a href=\"https://apps.apple.com/us/app/scriptable/id1405459188?uo=4\">Scriptable</a> to run it. I hope you enjoy it. Let me know if you have any suggestions for further improvements.</p>\n<h2>Credit Where It’s Due</h2>\n<p>As I said at the start, this project was hevilly inspired by Matt VanOrmer’s <a href=\"https://zmknox.com/resources/donation-widget/feedbackbot.jpg\">FeedbackBot command in the Relay FM Members Discord</a> and <a href=\"https://www.peerreviewed.io/blog/2021/8/20/a-home-screen-widget-for-relay-fms-annual-st-jude-fundraiser\">Pyto widget</a>. Additionally, I based some of my code (especially the progress bar) on <a href=\"https://twitter.com/juniorchen2012\">Juniorchen2012</a>’s widget from the Scriptable gallery.</p>\n",
      "date_published": "2021-08-21T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2021/05/thoughts-on-xdr/",
      "url": "https://zmknox.com/2021/05/thoughts-on-xdr/",
      "title": "Thoughts on XDR",
      "content_html": "<p>I recently upgraded my iPad from the 2018 11&quot; iPad Pro to the new 2021 iPad Pro 12.9&quot;<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn1\" id=\"fnref1\">[1]</a></sup>. I was interested in the speeds of the M1 chip, but what really got me to upgrade (and to go up in display size) was the new Liquid Retina XDR display. This 12.9&quot; display, with a resolution of 2732x2048@2x, has over 10,000 mini-LEDs to serve as a backlight laid out into 2,596 local dimming zones. That sounds fantastic! But what does it look like in practice?</p>\n<!-- more -->\n<h2>Contrast</h2>\n<p>The best case scenerio for a display is for the blacks to be truly black and whites to be very bright. an OLED display excels at the former, though sometimes fails to get bright enough (especially uniformly)<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn2\" id=\"fnref2\">[2]</a></sup>, and an LED-backlit LCD can generally excel at the latter. The mini-LED backlight on this iPad Pro does almost everything it can to do both.</p>\n<p>Here’s a picture of the iPad Pro in a dark room, displaying an entirely black PNG<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn3\" id=\"fnref3\">[3]</a></sup>.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/black.jpeg\" alt=\"A black image displayed on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Do you see it? No? Exactly. The iPad will turn off backlight zones entirely to reach these true black levels. If I pressed the side button to sleep the iPad, it would look exactly the same. For all intents and purposes, it appears that the iPad is not awake.</p>\n<p>If you’re in a more well-lit room, you can still tell the bezel apart from the display, as the black treatment they put on the glass has a deeper black point than the display when off. That’s true of basically every display bezel though. Nothing to be concerned about.</p>\n<p>In actual use, you can very quickly notice that the black point is far lower than prior iPad models. Gone is most of that LCD muddiness when using dark mode. It’s just black. I’m writing this right now in Kodex with a black background, and it looks fantastic.</p>\n<p>But it isn’t <em>perfect</em>.</p>\n<h2>Bloom</h2>\n<p>Because this display only has 2,596 zones to light 5,595,136 pixels, you’re inevitably going to come up against times when the backlight cannot perfectly reproduce what the display may want it to. This results in a bloom effect around objects in a scene. If you look at r/iPad, you may think that this bloom is awful and a dealbreaker for this display, but I don’t think that’s the case at all. So here’s the deal:</p>\n<ul>\n<li>If you’re in a well lit room: bloom is not a problem. The fall off of light into pure black is easily offset by the rest of the light in your environment</li>\n<li>If you’re looking at something that doesn’t have a true black background: bloom is generally not a problem. Maybe you’ll see some gradation in brightness, but its not <em>nearly</em> as stark as falling off to true black.</li>\n<li>Bloom is less noticable at lower brightness settings. If you crank the brightness to max, you’ll almost definitely see the effect of blooming more, as the backlight has to fall off further from bright to black.</li>\n<li>Larger, bright objects in a scene are not a big problem. Your eyes will naturally see a bit of a halo effect around brighter objects anyway<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn4\" id=\"fnref4\">[4]</a></sup>, so you can’t notice the light fall off.</li>\n<li>Light generally falls off smoothly, not abruptly. This means that you may sometimes see more of a blooming halo around something small than you may expect. I’m not sure if this is an algorithmic decision or a materials (diffuser, etc) decision, but I do kind of wish it was a bit more aggressive.</li>\n</ul>\n<p>Here are some examples<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn5\" id=\"fnref5\">[5]</a></sup> of the good and the bad. Note that the images I show are not entirely accurate to what you would see in real life, but were taken to give you a visual sense of the what I’m trying to explain. The images were taken on an iPhone 12 Pro with Smart HDR enabled, and were converted from HEIF to JPEG for display on the web.</p>\n<h3>Monsters, Inc. (Disney+, Dolby Vision)</h3>\n<p>Monsters, Inc. begins with a fun animatic to set the mood for the film, and to convince kids that this movie won’t be as scary as they might think. This opening looks <em>incredible</em> on the new iPad Pro’s display. The blacks are truly black and the motion is quick enough that any bloom that you may notice is almost immediately gone.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/monsters-inc.jpeg\" alt=\"Monsters, Inc opening scene on Liquid Retina XDR display\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>Star Wars: The Force Awakens (Disney+, Dolby Vision)</h3>\n<p>This movie shows two problems. First: it seems like the black point for this mastering isn’t quite true zero, and that it was matted into a 16:9 frame. This results in blooming around the 16:9 frame, even if there isn’t any content there. I don’t think this is a huge deal unless you’re in a pretty dark room, but is definitely noticable.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/the-force-awakens.jpeg\" alt=\"Star Wars: The Force Awakens on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Second, this film shows significant display issues when a scene has very moody lighting. This film begins with Poe speaking with an older man. That sequence looks <em>very bad</em> on this display. It’s extremely muddied, making it feel almost like a dream sequence. It’s probably the biggest stress test for this display, and I think the display fails. It looks bad.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/the-force-awakens-2.jpeg\" alt=\"Star Wars: The Force Awakens on Liquid Retina XDR again\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/the-force-awakens-3.jpeg\" alt=\"Star Wars: The Force Awakens on Liquid Retina XDR a third time\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/the-force-awakens-4.jpeg\" alt=\"Star Wars: The Force Awakens on Liquid Retina XDR a fourth time\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>Parts of this film do look really good in HDR, though.</p>\n<h3><a href=\"https://youtu.be/njX2bu-_Vw4\">LG OLED Test Footage</a> (YouTube, HDR)</h3>\n<p>This video was designed to showcase an OLED display, but I think it does a pretty good job at showing the benefits of this display too. You can see some bloom, especially in the opening shot, but a lot of it feels completely natural, as the objects are big and bright.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/lg-oled-demo.jpeg\" alt=\"LG OLED test footage on Lquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>Earth At Night In Color (Apple TV+, Dolby Vision)</h3>\n<p>I was curious to try a nature focused show. This one is focused on using special night vision camera tech to see life in the dark. I think the blooming seen here doesn’t detract from the experience at all. It feels similar to how accent lighting behind a TV can look cool.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/earth-at-night.jpeg\" alt=\"Earth At Night In Color on Lquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/earth-at-night-2.jpeg\" alt=\"Earth At Night In Color on Lquid Retina XDR again\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3><a href=\"https://youtu.be/GAuUtgBDjbI\">Late Night with Seth Meyers</a> (YouTube, SDR)</h3>\n<p>When watching a Late Night clip, I noticed that the local dimming definitely had a gradient fall off beyond the 16:9 viewing window. I don’t think it detracted from the viewing experience, but I think its worth showing as an example of what the blooming can show as in daily use.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/late-night.jpeg\" alt=\"Late Night with Seth Meyers on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>Settings App</h3>\n<p>The Settings app provides a good example of how the backlight reacts to standard UI in dark mode. There is some bloom in the corners and at the top for the text on a true black background, and a bit of bloom around each of the table view groups, but it isn’t a big issue.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/settings-app.jpeg\" alt=\"Settings app on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3>YouTube scrubber</h3>\n<p>The bottom scrubber bar of the YouTube app is a bit of an extreme example for blooming. The backlight exceeds the bar for quite a ways. In practice, this feels more like a design choice of the YouTUbe app than a real issue, but it’s definitely noticable.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/youtube-scrubber.jpeg\" alt=\"YouTube scrubber on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h3><a href=\"https://kidi.ng/wanna-see-a-whiter-white/\">Wanna see a whiter white?</a></h3>\n<p>This one’s a little weird. This webpage allows a device to, for the text in the center, display at a very high brightness beyond its typical white point. It works on my 2018 iPad Pro, and truly shines on my iPhone 12 Pro. Oddly, it doesn’t seem to work at all on this new iPad Pro<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fn6\" id=\"fnref6\">[6]</a></sup>.</p>\n<p><img src=\"https://zmknox.com/resources/ipad-xdr/whiter-white.jpeg\" alt=\"Wanna see a whiter white on Liquid Retina XDR\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h2>Overall Thoughts</h2>\n<p>I think, overall, this display is very good. In most cases, it will surely improve the viewing experience of anything you’re watching or creating. But it has some clear limitations. Backlight blooming definitely exists, and should definitely be considered if you’re trying to use this iPad as a creation tool. But in daily use, in typical room lighting, I don’t think you’ll notice it very often at all.</p>\n<p>Now if only they made this screen in an 11-inch size…</p>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Technically the “iPad Pro 12.9-inch (5th generation)”. <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>The latest iPhones 12 do an exceptional job at this for OLED displays. If you’re willing to watch a movie on a display that small, its a fantastic experience. <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>Federico also did this in his <a href=\"https://www.macstories.net/stories/ipad-pro-2021-review/\">excellent review on MacStories</a>, but I think it’s worth showing again to really drive this point home. If you want to see this for yourself, you can <a href=\"https://zmknox.com/resources/ipad-xdr/black-sample.png\">download the image I made and try it yourself</a> <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p>You can see this by looking at a bright object in front of a black background on an OLED display, which has no blooming. <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p>Most of my examples come from Disney+, Apple TV+, and YouTube, as those are services that I subscribe to which offer 4K HDR content. I’m wishing for the day that HBO Max starts universally supporting 4K HDR… <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p>Oddly, you <em>can</em> see it when <a href=\"https://zmknox.com/resources/ipad-xdr/whiter-white-control-center.jpeg\">blurred behind Control Center</a> <a href=\"https://zmknox.com/2021/05/thoughts-on-xdr/#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2021-05-23T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2020/11/stadium-browser-1-2/",
      "url": "https://zmknox.com/2020/11/stadium-browser-1-2/",
      "title": "Stadium Browser 1.2",
      "content_html": "<p>The past month has been quite a ride. My app blew up on <a href=\"https://www.reddit.com/r/Stadia/comments/j1ar15/use_stadia_on_ios_with_controller_support_easily/\">Reddit</a>, was reported on by <a href=\"https://www.theverge.com/2020/9/29/21493098/stadia-ios-browser-hack-reddit-stadium-app-store-apple-ipad-iphone-ipados\">big tech blogs</a>, and then was <a href=\"https://www.vice.com/en/article/m7aqwx/apple-will-remove-fan-app-that-allowed-stadia-streaming-on-ios\">removed by Apple</a>.</p>\n<p>I’m now happy to report that Stadium is officially back in the App Store! Introducing <a href=\"https://apps.apple.com/us/app/stadium-full-screen-browser/id1533596615\">Stadium 1.2</a>.</p>\n<!-- more -->\n<h2>What’s Different?</h2>\n<p>I had to remove my hooks between the GameController framework and WebKit. Apple said that I was “Using public APIs in a manner not prescribed by Apple”. This is unfortunate, but not a complete loss because…</p>\n<p>I also added a new “Override Custom User Agent” field, which will update the reported user agent <em>after page load</em>, which allows page JavaScript to read a different User Agent string from what you sent in your HTTP request.</p>\n<p>Finally, I (think I) fixed the tip jar.</p>\n<h2>How do I use Stadia with Stadium 1.2?</h2>\n<p>Set your Primary URL field to</p>\n<pre><code>https://stadia.google.com/home\n</code></pre>\n<p>Set your Custom User Agent String to</p>\n<pre><code>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36\n</code></pre>\n<p>Set your Override Custom User Agent String to</p>\n<pre><code>Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15\n</code></pre>\n<p>Disable “Require Full Screen Video” if you want to see Stadia’s menu overlays. I kept this as an option because forcing the video full screen may be better for smaller devices (hello there, iPhone 12 mini users).</p>\n<p>Before you can properly use Stadia, you’ll need to authenticate to Google. In the “…” menu, select “Authenticate Without Custom User Agent”, and when prompted, visit</p>\n<pre><code>https://accounts.google.com/\n</code></pre>\n<p>Once you’ve logged in and are at your account page, tap “Done”. You should be good to go!</p>\n<h2>What Controllers Work?</h2>\n<p>The Stadia controller will almost assuredly work, presuming that your device is on the same WiFi network as your controller.</p>\n<p>Bluetooth controllers <em>might</em> work. Using the “Override User Agent” I mentioned, Stadia should properly support the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API\">Gamepad API</a>, but iOS doesn’t always seem to pass that through. <strong>Please send me your feedback on how this works for you!</strong> I have gotten it to work more than once, but definitely not every time I tried.</p>\n<p>Keyboards might also work, but I don’t expect it to be a great experience.</p>\n<h2>Thank You!</h2>\n<p>Thank you to everyone who has been supportive of this project. I’ve received many kind words on Reddit, Discord, and from many in the press. This was a fun little project I built in a weekend on a whim, and I’m glad you all liked it.</p>\n",
      "date_published": "2020-11-06T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2020/09/stadium-full-screen-browser/",
      "url": "https://zmknox.com/2020/09/stadium-full-screen-browser/",
      "title": "Stadium Full Screen Browser",
      "content_html": "<p>Today, I launched a new web browser app for iOS. It displays webpages in full screen, allows you to change your user agent, has a way to authenticate without the user agent, and has game controller support. How strange!</p>\n<p>Stadium is specialized, but it happens to be great for using game streaming services! I posted a guide on how to use it with Google Stadia <a href=\"https://reddit.com/r/Stadia/comments/j1ar15/use_stadia_on_ios_with_controller_support_easily/\">on the /r/Stadia subreddit</a>.</p>\n<p>Stadium is free, so I encourage you to try it out with your favorite webpages! (or just want a browser that opens to <a href=\"https://html5zombo.com/\">zombo.com</a> every time).</p>\n",
      "date_published": "2020-09-28T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/",
      "url": "https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/",
      "title": "What If Developer ID Came To iOS?",
      "content_html": "<p>I have an iPhone. I prefer to use it, and its operating system, over other smartphones, but that means I am limited in how I can install apps onto it. Lately, with <a href=\"https://twitter.com/dhh/status/1272604267933655045\">all</a> of the <a href=\"https://www.businessinsider.com/apple-explains-why-xbox-game-pass-is-not-on-iphone-2020-8\">news</a> surrounding <a href=\"https://www.youtube.com/watch?v=euiSHuaw6Q4\">issues</a> in the App Store, I started to wonder: What would the iPhone and iPad look like if they allowed me to install apps from outside the App Store?</p>\n<p>Let’s imagine it…</p>\n<!-- more -->\n<p><img src=\"https://zmknox.com/resources/developer-id/sweet-solution.png\" alt=\"Steve Jobs at WWDC 2007, promoting web apps as a &quot;sweet solution&quot;\" :=\"\" class=\"img-thumbnail mx-auto d-block\"></p>\n<br>\n## Current Distribution Methods\n<p>Today, Apple <a href=\"https://help.apple.com/xcode/mac/current/#/dev31de635e5\">offers multiple ways to distribute apps</a> for the iPhone, but most of these are fairly specialized:</p>\n<ul>\n<li><strong>App Store</strong>: By far the most common distribution method. You sign in with your Apple ID, tap Get or Buy, pay Apple, and install your app. This has the fewest restrictions to a user, but requires developers pass App Review, and adhere to <a href=\"https://developer.apple.com/app-store/review/guidelines/\">their guidelines</a>.</li>\n<li><strong>TestFlight</strong>: Apple’s official solution for beta testing. Apps in TestFlight <em>generally</em> must follow the same guidelines as in the App Store, and must pass Beta App Review (which, while looser with some things, still adheres to the App Store guidelines). Test apps are limited to 10,000 testers, and have a 90 day expiration for every build.</li>\n<li><strong>Enterprise</strong>: Large companies can pay Apple (and be vetted) for an <a href=\"https://developer.apple.com/programs/enterprise/\">Enterprise Distribution Certificate</a> to distribute their internal apps. These apps can be distributed in any manner (Safari, a directory app, etc), but may only do so to those internal to the company.</li>\n<li><strong>Ad-Hoc</strong>: Members of the Developer Program can create an app binary that they can distribute however they want, but they can only be installed on devices registered in their developer account (of which you are extremely limited<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn1\" id=\"fnref1\">[1]</a></sup>). This can be useful if you need to deploy an app to many test devices.</li>\n<li><strong>Development</strong>: This allows for a quick build-and-run cycle for developing apps using Xcode. Builds expire in 7 days for free accounts, and in 1 year for members of the Developer Program. Of note: <a href=\"https://altstore.io/\">AltStore</a> uses this to distribute its apps.</li>\n</ul>\n<p>From looking at these options, their message is clear: If you’re broadly distributing an app, you need to use the App Store.</p>\n<p>The Mac, notably, has an additional option. Developers can use “Developer ID” to sign and notarize<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn2\" id=\"fnref2\">[2]</a></sup> their apps to distribute however they want. Apple then allows users the choice of if they want to allow these apps (defaulting to yes)<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn3\" id=\"fnref3\">[3]</a></sup>.</p>\n<p><img src=\"https://zmknox.com/resources/developer-id/macos-developer-id-preferences.png\" alt=\"Security preferences in macOS Catalina, showing options to allow apps only from the App Store, or also from Identified Developers\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<p>This seems to have worked out pretty well for the Mac. Let’s try bringing that concept to the iPhone.</p>\n<br>\n## New, But Familiar\n<p>Let’s imagine I’m tasked with adding support for Developer ID to iOS. What decisions would I make? What restrictions should I put in place? How will this all work?</p>\n<p>Here’s what I’d do:</p>\n<h4>1. Developer ID and Notarization Required</h4>\n<p>To broadly distribute an app outside the App Store, developers must still sign it and have it notarized by Apple. This is an automated vetting process which gives Apple the ability check for malicious apps, disabling them if necessary.</p>\n<p>Unsigned apps would not be allowed, in the name of security.</p>\n<h4>2. Limited Entitlements and Features</h4>\n<p>Developers would be limited in what entitlements they can use. This includes CarPlay, which even today developers must apply specially for, but may also include other limitations such as for HealthKit, HomeKit, or Sign In with Apple. These limitations will likely start somewhat restrictive, loosening over time.</p>\n<p>Apps would still be able to use iCloud and Apple’s Push Notification service, much like Mac apps can today.</p>\n<h4>3. User Opt-In</h4>\n<p>Before a user can install one of these apps, they must opt-in via Settings. This would look very similar to the Mac (though with a more “secure” default):</p>\n<p><img src=\"https://zmknox.com/resources/developer-id/developer-id-settings-concept.png\" alt=\"Concept images showing the flow to enable installation of apps by Identified Developers in App Store settings\" :=\"\" class=\"img-fluid mx-auto d-block\"></p>\n<h6>Concepts for Developer ID Settings</h6>\n<p>As an extra precaution, the user must enter their passcode before changing this setting, ensuring that they are the ones who want to make this change.</p>\n<h4>4. Install via Safari</h4>\n<p>Developer ID-signed Apps can be installed via a special kind of link in Safari. This is actually <a href=\"https://support.apple.com/guide/deployment-reference-ios/intro-to-distributing-in-house-apps-apda0e3426d7/web\">possible today for Enterprise, Ad-Hoc, and Development apps</a>. Developers provide a <code>itms-services://</code> link to their app manifest, and the system parses it, offering to install the app:</p>\n<p><img src=\"https://zmknox.com/resources/developer-id/ad-hoc-install.png\" alt=\"Installing my app AnotherLens via the web\" :=\"\" class=\"iphone-image mx-auto d-block\"></p>\n<h6>How installing Ad-Hoc apps works today. I tried it!</h6>\n<p>If we’re opening this up more broadly, however, we should expand on this functionality. Turn that simple pop up into a modal sheet including some more detailed text and possibly even some developer information to show who is distributing it.</p>\n<p><img src=\"https://zmknox.com/resources/developer-id/developer-id-install-concept.png\" alt=\"Concept image showing my proposed new modal for installing an app from Safari\" :=\"\" class=\"iphone-image mx-auto d-block\"></p>\n<h6>Concept for app installation with Safari</h6>\n<p>I would also force any call to open an install link to first bring Safari to the foreground. This makes sure that if you press “Cancel”, you’re in a known trusted app<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn4\" id=\"fnref4\">[4]</a></sup>. Apps providing third-party stores or options to update would still be able to link to install manifests, but you would need to go through Safari first.</p>\n<h4>5. No App Store Niceties</h4>\n<p>Developers must provide their own payment systems, their own validation checks, and their own update check mechanisms. They would not be able to use Apple’s In-App Purchase system, the App Store’s automatic updates, or StoreKit’s receipt validation.</p>\n<p>There would likely be a market for third parties to build replacements for these, like how the <a href=\"https://sparkle-project.org/\">Sparkle</a> framework can automatically check for Mac app updates on launch.</p>\n<h4>6. No Outside Promotion</h4>\n<p>Apps in the App Store would not be allowed to promote apps outside the App Store. This includes links in your UI, advertising, and in webpages you link to directly<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn5\" id=\"fnref5\">[5]</a></sup>. This rule is in a similar vein to Apple’s rejection of apps linking out to websites for in-app purchase. Once using an App Store app, any digital purchases made within it are using the App Store.</p>\n<h4>7. XProtect</h4>\n<p>Apple’s XProtect anti-malware system, used on the Mac, will be put into place on iOS. Any app which Apple flags as malicious may be disabled remotely. In the past, this has almost always been used for intentionally malicious software<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn6\" id=\"fnref6\">[6]</a></sup>.</p>\n<br>\n<h2>What Are the Benefits?</h2>\n<p>With Developer ID support, software distribution would become far closer to how it is on the Mac, where Apple only enforces its review guidelines within its App Store. This will allow for lots of new opportunities:</p>\n<ul>\n<li>Microsoft could release a version of their <a href=\"https://www.theverge.com/2020/8/6/21358074/microsoft-xcloud-cloud-gaming-condems-apple-app-store-rules-iphone-ios\">Xbox Game Pass app</a> with support for their new xCloud streaming service.</li>\n<li>Epic Games could sell V-Bucks using <a href=\"https://www.epicgames.com/fortnite/en-US/news/the-fortnite-mega-drop-permanent-discounts-up-to-20-percent\">their own payment system in Fortnite</a><sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fn7\" id=\"fnref7\">[7]</a></sup>.</li>\n<li>Apple wouldn’t have to worry about losing iPhone sales over the US government forcing <a href=\"https://www.theverge.com/2020/8/8/21358941/wechat-ban-apple-china-business-trump-tariffs-trade-manufacturing-impact\">foreign corporations like Tencent off of their platform</a>. Tencent could simply release WeChat themselves.</li>\n<li>Riley Testut could distribute his <a href=\"https://deltaemulator.com/\">Delta emulator</a> without needing to use AltStore.</li>\n</ul>\n<p>Additionally, this will provide at least some level of competitive pressure on the App Store to innovate. Developers could still choose to use the App Store for all of its convenience, built-in support for features like In-App Purchase, and trust from users, but it would no longer be the <em>only</em> choice, which may cause Apple to update their rules and rates to compete.</p>\n<br>\n## What Are the Consequences?\n<p>The biggest consequence: malware will probably exist. Will it be common? I don’t know. But it will probably exist, and that’s something Apple has done an admirable job at keeping out of the App Store up to now. I believe that most of the warnings and opt-in requirements in my concept will mitigate a lot of user negligence, but it definitely won’t stop it all.</p>\n<p>From Apple’s perspective, the biggest consequence is losing control. Since the launch of the App Store, Apple has had final say on what can and what can’t be mass distributed to iPhone users, and this changes that. That’s a huge change that they would have to come to terms with.</p>\n<br>\n## Would This Actually Happen?\n<p>The rising pressure against the App Store might cause them to implement a solution like this, but the Apple of right now seems unwilling to budge on their App Store policies. We’ll just have to wait and see what the future holds…</p>\n<br>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>Specifically, 100 per device type, which you can revoke when you renew your membership to reclaim slots. Interestingly, it counts iPods separately from iPhones, so if you need a new test device, consider an iPod touch? <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>Notarization isn’t strictly necessary, but it is now required for new builds of Developer ID-signed apps as of macOS Catalina. <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>Developers can also choose not to sign their app at all. This requires users use the “open” command on their app before first launch, and is generally discouraged. <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p>Currently, install links will show a simple alert controller, which is useful for enterprise app directory apps, but could lead to popup loops by a malicious app. <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p>User-generated content (tweets in a Twitter app, for example) would be fine. <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p>One incident where Apple shut a legitimate app down was when <a href=\"https://techcrunch.com/2019/07/10/apple-silent-update-zoom-app/\">Zoom installed a local web server</a> to make it easier to join meetings, which ended up being a big security risk. <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p>And yes, create their own store on iOS. Competition is a good thing! (I can’t imagine they’d like the scary looking ⚠️ I put on the install screen though) <a href=\"https://zmknox.com/2020/08/what-if-developer-id-came-to-ios/#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2020-08-17T17:00:00Z"
    }
    ,
    {
      "id": "https://zmknox.com/2020/05/edison-mails-security-nightmare/",
      "url": "https://zmknox.com/2020/05/edison-mails-security-nightmare/",
      "title": "Edison Mail&#39;s Security Nightmare",
      "content_html": "<p>Last Saturday, I woke up, updated some apps on my phone, and opened my email app, Edison Mail. I was greeted by a splash page telling me about a new account sync feature<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn1\" id=\"fnref1\">[1]</a></sup>, and to choose which email address was my primary account of the two I had in the app. I selected my Gmail address and tapped continue.</p>\n<p>After I got into my inbox and my email reloaded, something was off. I noticed a notification email from Twitch that a streamer that I don’t watch had gone live. Tapping into the email, I noticed both the username and the email address it was directed to weren’t mine. Going back into my inbox, I noticed a lot of emails that weren’t for me. I opened the sidebar, and there it was. In addition to my two email accounts was a Gmail account that wasn’t mine. Something had gone horribly wrong.</p>\n<p>At first I thought it was a fluke bug—a very very bad fluke bug, but a fluke nonetheless. I emailed Edison support and promptly revoked access to my accounts to the app. It soon became clear that this was not just a fluke, but an <em>incredibly bad</em> security issue.</p>\n<!-- more -->\n<h2>How Edison Responded</h2>\n<p>About 2 hours after <a href=\"https://twitter.com/zmknox/status/1261645534445604865\">I tweeted about the issue</a>, Edison support woke up on Twitter and started <a href=\"https://twitter.com/edison_apps/status/1261681576456237056\">replying to concerns from myself and others</a> that they were looking into the issue. They later explained that the update had gone out late the night prior<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn2\" id=\"fnref2\">[2]</a></sup>, and would be being pulled.</p>\n<p>After they had time to shut down the bug and analyze the issue, Edison <a href=\"https://medium.com/changing-communications/edison-mail-resolved-software-issue-that-potentially-exposed-ios-user-accounts-6-480-potentially-39116d7ae261\">wrote a blog post</a> (and sent an email to users affected) explaining 6,480 users<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn3\" id=\"fnref3\">[3]</a></sup> of their iOS app were affected by this issue. They further explained that users of this version would be locked out and to sign into Edison mail again using the newly released bug fix.</p>\n<p>Edison also briefly explained the nature of the bug. It effectively linked you and another user together into one Edison sync account. You could access their email, and they could access yours. Do note that this is <em>full access</em>, not simply reading, but writing, deleting, forwarding, etc. Everything you would need to impersonate your new pen pal.</p>\n<p>Finally, Edison’s blog post explains that “No passwords or credentials were exposed or compromised”. While this may be <em>technically</em> true, it doesn’t mean much when the thing another user <em>did</em> gain access to was my email inbox. Everything goes through email! Receipts, notifications, account recovery, correspondence. It’s probably the number one thing I don’t want to be compromised.</p>\n<h2>What Edison Hasn’t Told Us</h2>\n<p>In addition to their’s blog post, Edison’s founder, Hetal Pandya, found her way into <a href=\"https://twitter.com/spicycarot/status/1262880427640938496\">my Twitter mentions</a>, trying to defend Edison mail from criticism about their past behavior about neglecting user privacy<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn4\" id=\"fnref4\">[4]</a></sup>. Seeing as she reached out to me, I <a href=\"https://twitter.com/zmknox/status/1262882238561234950\">sent her a tweet</a> with questions I had previously sent to Edison support<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn5\" id=\"fnref5\">[5]</a></sup> (who still has yet to reply to my support requests). In our exchange, she replied multiple times, but didn’t really answer my questions. This seems to be a trend with all of their responses: to give the minimum possible answer, and dodge the hard questions. As a result:</p>\n<ul>\n<li>We don’t know what specifically in their architecture made this bug possible.</li>\n<li>We don’t know how they missed this in testing, despite stating that they have a staging environment.</li>\n<li>We don’t know what specific measures they’re putting in place to make sure this can never happen again.</li>\n</ul>\n<p>Edison publishes a <a href=\"https://www.edison.tech/privacy-commitments\">privacy commitment</a> which reads like a joke now. They claim that “Security is paramount” and “We keep mail private”, which simply weren’t true here. They also claim GDPR and CCPA compliance, which also went straight out the window. Nobody should be trusting Edison with their email until and unless they explain their email security architecture and design. I believe they are capable of explaining this, and they’ve gone in depth before about their data collection design and practices. I look forward to reading your technical deep-dive, Edison team.</p>\n<p>But while we wait for them, let’s instead analyze what could have been done to stop this bug from happening.</p>\n<h2>How Could This Have Been Avoided?</h2>\n<p>I’m a software developer. I know bugs happen. I also know that bugs this big are capable of being caught before they hit production. Let’s look at ways this bug could have been avoided.</p>\n<h3>Finding The Bug In Testing</h3>\n<p>First, lets assume you’re a new engineer at Edison, and are tasked with working on this WIP feature. You aren’t an architect, and you didn’t choose how your databases are structured. What can you do to avoid bugs like this? Testing!</p>\n<p>I primarily work on building systems which make it easier for developers to do testing, so I’m certainly a little biased, but I truly think testing can be a valuable tool when dealing with complicated systems like this.</p>\n<p>You can test from the smallest scale to the largest. Unit tests will check an individual function’s abilities. Integration tests test how those functions work together. Acceptance tests will run those functions against live (usually development or staging) infrastructure to make sure it all still works together. Automated testing tools are all over the place these days, and continuous integration pipelines make it easy to run them on every commit or pull request.</p>\n<p>For this bug in particular, I would test with some dummy Edison Mail users in a staging environment, setting them up with the new sync service to verify that it is working correctly. This could be automated to be done before any pull request is merged or app update is released.</p>\n<p>Additionally, I would <a href=\"https://en.wikipedia.org/wiki/Eating_your_own_dog_food\">dogfood</a> the sync feature on my own team for some time to make sure we weren’t experiencing any bugs like this. This process is more manual, but could potentially find edge cases an automated test cannot.</p>\n<h3>Making The Bug Impossible</h3>\n<p>If you’re building this infrastructure from scratch, however, the best approach fixing this bug is to make this kind of bug impossible to create in the first place, keeping each user’s data effectively sandboxed from each other.</p>\n<p>Ideally, you would let a bigger fish handle your credential storage. iOS allows for this with <a href=\"https://support.apple.com/en-gb/HT204085\">iCloud Keychain</a>, which will handle syncing for you.</p>\n<p>But if your app, like Edison’s, is cross platform, you may want to keep these credentials on a server independent of a platform vendor. To do this, the app could store encrypted credentials on the server created using a key on a local device. When a user wants to log in on a new device, they could send a notification to an active device to approve the login, else require them to re-login to their email accounts. This system makes sure that Edison never has your credentials to share<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn6\" id=\"fnref6\">[6]</a></sup>, and that your account can only be activated on a new device with your consent.</p>\n<h2>Thoughts on Edison</h2>\n<p>In case it wasn’t clear: I’m furious with Edison Mail over this bug. Nobody should ever be given access to thee wrong email account by mistake. No apology can wash this away from their reputation. They will forever be the email app that strayed user trust. I’m certainly not trusting them with my email anymore, and neither should you.</p>\n<p>Why, then, am I so persistent at trying to get answers on what happened from Edison Mail? Because users deserve to know how their data was mis-handled. Users should know what went wrong, and how its being fixed. This isn’t a “sweep it under the rug” moment for Edison, and they need to know that.</p>\n<p>Now if you’ll excuse me, I’ll be over here looking for a new email app to call home<sup class=\"footnote-ref\"><a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fn7\" id=\"fnref7\">[7]</a></sup>.</p>\n<hr>\n<hr class=\"footnotes-sep\">\n<section class=\"footnotes\">\n<ol class=\"footnotes-list\">\n<li id=\"fn1\" class=\"footnote-item\"><p>I was familiar with a similar sync feature from <a href=\"https://apps.apple.com/us/app/spark-email-app-by-readdle/id997102246\">Spark</a>, so I didn’t immediately have concerns about this. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref1\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn2\" class=\"footnote-item\"><p>Specifically “10:50 PM PST”, which is not currently a valid time zone due to <a href=\"https://en.wikipedia.org/wiki/Pacific_Time_Zone#Daylight_time\">Daylight Saving Time</a>. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref2\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn3\" class=\"footnote-item\"><p>Edison has not made it clear whether this means “users” or “email accounts”. I had two accounts loaded into Edison, and both of them have gotten the breach email. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref3\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn4\" class=\"footnote-item\"><p>In February, Edison had <a href=\"https://www.vice.com/en_us/article/pkekmb/free-email-apps-spying-on-you-edison-slice-cleanfox\">a bit of a privacy scandal</a> regarding how they use user data (specifically around their purchases) to help drive their other business: selling analytics. In my opinion, while these concerns are valid, I was willing to, at the time, trust that the toggles in Edison Mail’s settings to turn off this tracking behavior would do just that. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref4\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn5\" class=\"footnote-item\"><p>In case anyone involved deletes these tweets in the future, <a href=\"https://zmknox.com/resources/edison/conversation1.jpg\">here are some screenshots</a> of <a href=\"https://zmknox.com/resources/edison/conversation2.jpg\">these conversations</a>. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref5\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn6\" class=\"footnote-item\"><p>They would have access to an <em>encrypted</em> version of your credentials, but, so long as they are done with a secure enough encryption algorithm, this is a non-issue unless you’re being pursued by state-sponsored actors. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref6\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n<li id=\"fn7\" class=\"footnote-item\"><p>I’m still crying over Mailbox’s shutdown. <a href=\"https://zmknox.com/2020/05/edison-mails-security-nightmare/#fnref7\" class=\"footnote-backref\">↩︎</a></p>\n</li>\n</ol>\n</section>\n",
      "date_published": "2020-05-23T17:00:00Z"
    }
    
  ]
}