A place to cache linked articles (think custom and personal wayback machine)
Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

index.html 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang="fr">
  4. <!-- Has to be within the first 1024 bytes, hence before the `title` element
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset="utf-8">
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name="viewport" content="width=device-width,initial-scale=1">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>Outdated vs. Complete (archive) — David Larlet</title>
  13. <meta name="description" content="Publication mise en cache pour en conserver une trace.">
  14. <!-- That good ol' feed, subscribe :). -->
  15. <link rel="alternate" type="application/atom+xml" title="Feed" href="/david/log/">
  16. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  17. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons2/apple-touch-icon.png">
  18. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons2/favicon-32x32.png">
  19. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons2/favicon-16x16.png">
  20. <link rel="manifest" href="/static/david/icons2/site.webmanifest">
  21. <link rel="mask-icon" href="/static/david/icons2/safari-pinned-tab.svg" color="#07486c">
  22. <link rel="shortcut icon" href="/static/david/icons2/favicon.ico">
  23. <meta name="msapplication-TileColor" content="#f7f7f7">
  24. <meta name="msapplication-config" content="/static/david/icons2/browserconfig.xml">
  25. <meta name="theme-color" content="#f7f7f7" media="(prefers-color-scheme: light)">
  26. <meta name="theme-color" content="#272727" media="(prefers-color-scheme: dark)">
  27. <!-- Documented, feel free to shoot an email. -->
  28. <link rel="stylesheet" href="/static/david/css/style_2021-01-20.css">
  29. <!-- See https://www.zachleat.com/web/comprehensive-webfonts/ for the trade-off. -->
  30. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  31. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  32. <link rel="preload" href="/static/david/css/fonts/triplicate_t4_poly_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" crossorigin>
  33. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_regular.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  34. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_bold.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  35. <link rel="preload" href="/static/david/css/fonts/triplicate_t3_italic.woff2" as="font" type="font/woff2" media="(prefers-color-scheme: dark)" crossorigin>
  36. <script>
  37. function toggleTheme(themeName) {
  38. document.documentElement.classList.toggle(
  39. 'forced-dark',
  40. themeName === 'dark'
  41. )
  42. document.documentElement.classList.toggle(
  43. 'forced-light',
  44. themeName === 'light'
  45. )
  46. }
  47. const selectedTheme = localStorage.getItem('theme')
  48. if (selectedTheme !== 'undefined') {
  49. toggleTheme(selectedTheme)
  50. }
  51. </script>
  52. <meta name="robots" content="noindex, nofollow">
  53. <meta content="origin-when-cross-origin" name="referrer">
  54. <!-- Canonical URL for SEO purposes -->
  55. <link rel="canonical" href="https://vivqu.com/blog/2022/09/25/outdated-apps/">
  56. <body class="remarkdown h1-underline h2-underline h3-underline em-underscore hr-center ul-star pre-tick" data-instant-intensity="viewport-all">
  57. <article>
  58. <header>
  59. <h1>Outdated vs. Complete</h1>
  60. </header>
  61. <nav>
  62. <p class="center">
  63. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  64. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  65. </svg> Accueil</a> •
  66. <a href="https://vivqu.com/blog/2022/09/25/outdated-apps/" title="Lien vers le contenu original">Source originale</a>
  67. </p>
  68. </nav>
  69. <hr>
  70. <p>On August 22nd, I got an email out of the blue from Apple that notified me that I had a new App Review message. It was for my app, <a href="https://worldanimalsapp.com/">WorldAnimals</a>, a light-hearted game for guessing animal onomatopoeia sounds in different languages.</p>
  71. <p>Usually, you receive a message after you submit a new version to the App Store for review. The reviewer has found something wrong and your app is rejected. The notice explains where you have violated the <a href="https://developer.apple.com/app-store/review/guidelines/">App Store Review Guidelines</a> and how you can rectify the issue to get your app update approved. Maybe the app is crashing, the reviewer can’t log in to test, or god forbid the update description mentions the evil rival mobile platform–these are all actual reasons I have been rejected by the App Store.</p>
  72. <p>The relationship between developers and App Store reviewers is tense at best. Most people are trying to build well-designed, useful mobile apps. Apple has instituted App Store reviews to maintain a high-quality bar for apps and weed out spammy or nefarious actors, using human evaluators to test individual apps and provide direct feedback. However, malicious apps are relatively rare; arguably, Apple doesn’t do a great job filtering them out anyway. So for the vast majority of developers, App Store reviews add an additional layer of friction and time to shipping updates. And then in the inevitable case when you need to push out an emergency fix for crashes happening to your app users, the App Store review process goes from an inconvenient annoyance to an outright roadblock to improving the user experience.</p>
  73. <div class="twitter-container"><blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">I think the asymmetry of App Review is still lost on Apple. For indie developers our hopes and dreams (and sometimes our finances) hang in the balance, for the App Review team it’s just another app rejection among tens of thousands. I know they think they get it, they just don’t. <a href="https://t.co/YSsj2zyilA">https://t.co/YSsj2zyilA</a></p>&mdash; David Barnard (@drbarnard) <a href="https://twitter.com/drbarnard/status/1492173728855252998?ref_src=twsrc%5Etfw">February 11, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
  74. <p>I’ve been around the block, both as an indie developer and a full-time-employed mobile developer. I have released fun, tiny games like WorldAnimals and production SaaS mobile apps. When I was at Pinterest, I helped in communications with our dedicated App Store representative who would expedite Pinterest app updates through the review process. I have seen first-hand the lack of support for indie apps compared to the white glove experience that large companies get. Suffice it to say, I probably have above-average knowledge of how this whole process works.</p>
  75. <p>And <em>still</em>, I was surprised to receive an App Review message. I hadn’t submitted a new update for WorldAnimals. The app was still working well, with zero crashes and a handful of new downloads every month. My boss had even shown me last week that he had downloaded my app on his phone for his daughter–we played the game together during a work meeting and laughed at the silly animal sounds. In my mind, there was no reason I should be receiving a vaguely threatening message from Apple’s App Review system.</p>
  76. <p>Well, it turns out, Apple’s problem with my app was the fact that I wasn’t updating it.</p>
  77. <hr class="section-divider" />
  78. <p>I opened the message and was greeted with the <a href="https://developer.apple.com/news/?id=gi6npkmf">“App Store Improvement Notice”</a>. I was essentially told that I hadn’t updated my app in three years and now it counts as outdated. I needed to update the app within 90 days or it would get automatically taken down.</p>
  79. <p><br />
  80. <img src="https://vivqu.com/assets/img/posts/outdated-apps/improvement-notice.png" alt="app-notice" />
  81. <em>The message I received from the App Store Review told me my app was “oudated”.</em></p>
  82. <p><br />
  83. Never mind the fact that my app has a 5-star rating and was still being downloaded, with no complaints from any of my users. Also disregard the fact that I had other highly-rated apps up on the App Store, some of which had been updated much more recently than July 2019, clearly showing that I have not abandoned these apps entirely. If there had been an actual reviewer who checked my outdated app, they would have discovered that I architected the app from the beginning to dynamically scale the UI so it resizes to fit the latest iPhone devices. All these could be signals that indicate to Apple that this is not a garbage-filled scam app that is lowering the quality of their App Store.</p>
  84. <p><a href="https://www.theverge.com/2022/4/23/23038870/apple-app-store-widely-remove-outdated-apps-developers">Many other developers have complained</a> about this draconian measure. The decision to remove outdated apps places a disproportional burden on indie developers and hobbyists because they might not have time or resources to revisit these old apps. Just having an active Apple developer membership <a href="https://developer.apple.com/support/compare-memberships/">costs $99 a year</a>. It feels a bit like extortion when the platform, which you already paid to publish your app once, is now requiring you to continue renewing your membership to make that same app stay available.</p>
  85. <p>Beyond the financial cost, what is the most insulting to me about Apple’s policy is how poorly thought out their measure of “quality” is for apps. The message contains two separate statements about my app: (1) it hasn’t been updated in three years, and (2) it doesn’t meet a “minimum download threshold.” Fixing either of those so-called problems doesn’t magically mean my app will be a high-quality, positive experience for users.</p>
  86. <p>There is absolutely no requirement for what is contained in the app update since Apple only states, “the app will remain available if an app update is submitted and approved within 90 days.” At least in Google’s case, the Play Store requires an update to their <a href="https://www.theverge.com/2022/4/7/23014518/google-play-store-cracks-down-on-outdated-apps">minimum target level API</a> which ensures that developers use a newer version of Android Studio.</p>
  87. <p>The intention behind Apple and Google’s policies is to force developers to make sure their apps run successfully on the latest devices and operating systems. Apple claims that the App Store improvement process will improve user experience because “keeping apps up to date to conform with modern screen sizes, SDKs, APIs […] ensures users can have a great experience with any app they get from the App Store”. But my app was already working correctly, making this update useless and performative.</p>
  88. <p><br />
  89. <img src="https://vivqu.com/assets/img/posts/outdated-apps/worldanimals.png" alt="app-screenshot" />
  90. <em>My app was working well even on the latest iPhone and iPad devices.</em></p>
  91. <p><br />
  92. Even more frustrating is that while the app itself runs fine on all the latest end-user devices, Apple’s development ecosystem has changed rapidly over the last three years. My old code simply did not work anymore with the latest versions of Xcode. So I had to spend four hours on Saturday upgrading all my platform libraries simply so that I could compile the damn app. And all this effort was ultimately in order to just change the build version number from “1.0” to “1.1”. The UI and functionality remained unchanged since it had already been working as intended!</p>
  93. <div class="twitter-container"><blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">I&#39;m sitting here on a Friday night, working myself to to bone after my day job, trying my best to scrape a living from my indie games, trying to keep up with Apple, Google, Unity, Xcode, MacOS changes that happen so fast my head spins while performing worse on older devices.</p>&mdash; Protopop Games (@protopop) <a href="https://twitter.com/protopop/status/1517702095482331137?ref_src=twsrc%5Etfw">April 23, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
  94. <p>And to add insult to injury, Apple tells me I could have avoided all this pain if my app was being downloaded above a “minimum download threshold”. The policy is completely silent on what this download number needs to be to avoid getting flagged as outdated–is it hundreds or thousands or hundreds of thousands of downloads a month? This seems like an explicit carve-out in the policy for apps with major marketing budgets. According to <a href="https://www.businessofapps.com/marketplace/app-marketing/research/app-marketing-cost/">industry research</a>, the total marketing spend for app installs is projected to be $61.1 billion in 2022. So it seems like an incredibly bad faith argument to claim that a higher download rate means better quality apps. All a higher download rate means is that more money was probably spent to market and optimize app installs.</p>
  95. <div class="twitter-container"><blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">Apple also removed a version of my FlickType Keyboard that catered specifically to the visually impaired community, because I hadn&#39;t updated it in 2 years.<br /><br />Meanwhile, games like Pocket God have not been updated by the developers for 7 years now: <a href="https://t.co/3azyIydty7">https://t.co/3azyIydty7</a> <a href="https://t.co/n36rvHvF4H">pic.twitter.com/n36rvHvF4H</a></p>&mdash; Kosta Eleftheriou (@keleftheriou) <a href="https://twitter.com/keleftheriou/status/1517907548623437824?ref_src=twsrc%5Etfw">April 23, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
  96. <p>The overall message that this crackdown sends is that Apple doesn’t care about about small and independent developers. For instance, the App Store could factor in:</p>
  97. <ul>
  98. <li>App Store ratings and reviews</li>
  99. <li>Active developer membership</li>
  100. <li>Historical behavior of the developer (ie. updating other apps)</li>
  101. <li>Number of crashes</li>
  102. <li>Number of active sessions, especially for the latest devices and platform versions</li>
  103. </ul>
  104. <p>These are all metrics that the Apple already automatically tracks, made available to the developer through the <a href="https://developer.apple.com/app-store-connect/">App Store Connect</a> portal.</p>
  105. <p><br />
  106. <img src="https://vivqu.com/assets/img/posts/outdated-apps/appstoreconnect-overview.png" alt="appstoreconnect-overview" />
  107. <em>App Store Connect shows an overview of your app and includes a variety of metrics, including active sessions and crashes.</em></p>
  108. <p><br />
  109. <img src="https://vivqu.com/assets/img/posts/outdated-apps/appstoreconnect-metrics.png" alt="appstoreconnect-metrics" />
  110. <em>The metrics tab enables developers to break down their app metrics by device and platform version.</em></p>
  111. <p><br />
  112. It would be almost zero cost to Apple to add these additional checks to their filter for outdated apps–after all, the data already exists in their system. So it doesn’t seem like mere oversight that the policy neglected to use a more nuanced set of inputs, it feels like Apple is actively degrading the developer experience because they consider us worthless to their platform.</p>
  113. <hr class="section-divider" />
  114. <p>In the end, Apple will always prefer the needs of mega-apps that have millions of downloads since these apps generate the bulk of the revenue for the App Store. The impact of the App Store Improvement policy is nonexistent for VC-funded companies. The high-growth apps trying to blitzscale their way to product-market-fit have been churning out regular updates all along with their massive teams of mobile developers. Small apps and their developers will need to conform to the whims of the platform or else disappear entirely–this is and always has been the risk of building software inside a walled garden.</p>
  115. <p>I wish that the App Stores had a concept of a “complete” app that does not need further updates. Emilia (<a href="https://twitter.com/lazerwalker">@lazerwalker</a>) put it really well by describing certain types of software as “finished artworks”:</p>
  116. <div class="twitter-container"><blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">.<a href="https://twitter.com/Apple?ref_src=twsrc%5Etfw">@apple</a> is removing a few of my old games b/c they have “not been updated in a significant amount of time”<br /><br />Games can exist as completed objects! These free projects aren’t suitable for updates or a live service model, they’re finished artworks from years ago. <a href="https://t.co/iflH70j7q4">pic.twitter.com/iflH70j7q4</a></p>&mdash; emilia ✨ (@lazerwalker) <a href="https://twitter.com/lazerwalker/status/1517849201148932096?ref_src=twsrc%5Etfw">April 23, 2022</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
  117. <p>Games are not the only type of software that doesn’t need updates. Author Robin Sloan has a wonderful “tap essay” named <a href="https://apps.apple.com/us/app/fish-a-tap-essay/id510560804">Fish</a>. The app was last updated in June 2018, therefore “outdated” by Apple’s standards. Sloan has probably contested the removal so it’s still available on iOS, and there is always an option to <a href="https://www.robinsloan.com/fish/">read it on macOS or Windows if needed</a>. Other creators might not be as lucky or have the money/time to provide their work on different platforms.</p>
  118. <p>It will be very difficult to reverse the incentives for Apple and Google and get them to start caring about these small gems. They have been killing apps from the very start, in favor of the newest mobile devices and functionality. There is a whole trend on Tiktok <a href="https://www.tiktok.com/tag/oldapps?is_from_webapp=1&amp;sender_device=pc">#oldapps</a> that are simply videos of people booting up old phones to play with apps that no longer can be run and reminisce about the joy that these apps created.</p>
  119. <div class="twitter-container"><blockquote class="tiktok-embed" cite="https://www.tiktok.com/@karalof/video/7124015574182546734" data-video-id="7124015574182546734" style="border-left: 0px; max-width: 605px;min-width: 325px;"> <section> <a target="_blank" title="@karalof" href="https://www.tiktok.com/@karalof?refer=embed">@karalof</a> diving deep back into middle school 😅💅💖 <a title="fyp" target="_blank" href="https://www.tiktok.com/tag/fyp?refer=embed">#fyp</a> <a title="ipodtouch" target="_blank" href="https://www.tiktok.com/tag/ipodtouch?refer=embed">#ipodtouch</a> <a title="apple" target="_blank" href="https://www.tiktok.com/tag/apple?refer=embed">#apple</a> <a title="middleschool" target="_blank" href="https://www.tiktok.com/tag/middleschool?refer=embed">#middleschool</a> <a title="nostalgia" target="_blank" href="https://www.tiktok.com/tag/nostalgia?refer=embed">#nostalgia</a> <a title="nostalgic" target="_blank" href="https://www.tiktok.com/tag/nostalgic?refer=embed">#nostalgic</a> <a title="biginkenergy" target="_blank" href="https://www.tiktok.com/tag/biginkenergy?refer=embed">#BigInkEnergy</a> <a title="retro" target="_blank" href="https://www.tiktok.com/tag/retro?refer=embed">#retro</a> <a title="elementaryschool" target="_blank" href="https://www.tiktok.com/tag/elementaryschool?refer=embed">#elementaryschool</a> <a title="ipod" target="_blank" href="https://www.tiktok.com/tag/ipod?refer=embed">#ipod</a> <a title="apps" target="_blank" href="https://www.tiktok.com/tag/apps?refer=embed">#apps</a> <a title="angrybirds" target="_blank" href="https://www.tiktok.com/tag/angrybirds?refer=embed">#angrybirds</a> <a title="kik" target="_blank" href="https://www.tiktok.com/tag/kik?refer=embed">#kik</a> <a title="appzilla" target="_blank" href="https://www.tiktok.com/tag/appzilla?refer=embed">#appzilla</a> <a target="_blank" title="♬ Say Hey (I Love You) (feat. Cherine Tanya Anderson) - Michael Franti &#38; Spearhead" href="https://www.tiktok.com/music/Say-Hey-I-Love-You-feat-Cherine-Tanya-Anderson-6946697184817465346?refer=embed">♬ Say Hey (I Love You) (feat. Cherine Tanya Anderson) - Michael Franti &#38; Spearhead</a> </section> </blockquote></div>
  120. <script async="" src="https://www.tiktok.com/embed.js"></script>
  121. <p>Day-by-day, month-by-month, the App Store will get a little less rich and vibrant as apps start being designated as outdated and get removed. Another consequence of this hostile policy is that indie and hobbyist developers may stop building mobile apps. After all, the web is fundamentally a more stable place for experimental software and “finished artworks”, since backwards-compatibility is the gold standard and apps can run indefinitely.</p>
  122. <p>After 4 hours of work to re-compile my app and 44 hours waiting in the review queue, WorldAnimals is now updated to a new version. I am safe for at least another three years before getting automatically flagged for removal. Unless, that is, Apple decides there is a new threshold for “outdated” and change their policy once again.</p>
  123. <p><br />
  124. <img src="https://vivqu.com/assets/img/posts/outdated-apps/appstore-submission.png" alt="appstore-submission" />
  125. <em>WorldAnimals is now available for another three years (hopefully).</em></p>
  126. <p><br />
  127. I still love mobile development–a large part of my engineering career has been building mobile apps of all sizes, for small hobby side projects and for huge unicorn companies. I am proud of WorldAnimals and want to make sure it’s still available for download. But this outdated policy will make me seriously think twice about building fun, experimental apps on iOS in the future.</p>
  128. <hr class="section-divider" />
  129. <footer>This article was last updated on 9/25/2022. v1 is 1,806 words and took 5.25 hours to write and edit.</footer>
  130. </article>
  131. <hr>
  132. <footer>
  133. <p>
  134. <a href="/david/" title="Aller à l’accueil"><svg class="icon icon-home">
  135. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-home"></use>
  136. </svg> Accueil</a> •
  137. <a href="/david/log/" title="Accès au flux RSS"><svg class="icon icon-rss2">
  138. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-rss2"></use>
  139. </svg> Suivre</a> •
  140. <a href="http://larlet.com" title="Go to my English profile" data-instant><svg class="icon icon-user-tie">
  141. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-user-tie"></use>
  142. </svg> Pro</a> •
  143. <a href="mailto:david%40larlet.fr" title="Envoyer un courriel"><svg class="icon icon-mail">
  144. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-mail"></use>
  145. </svg> Email</a> •
  146. <abbr class="nowrap" title="Hébergeur : Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33184162340"><svg class="icon icon-hammer2">
  147. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-hammer2"></use>
  148. </svg> Légal</abbr>
  149. </p>
  150. <template id="theme-selector">
  151. <form>
  152. <fieldset>
  153. <legend><svg class="icon icon-brightness-contrast">
  154. <use xlink:href="/static/david/icons2/symbol-defs-2021-12.svg#icon-brightness-contrast"></use>
  155. </svg> Thème</legend>
  156. <label>
  157. <input type="radio" value="auto" name="chosen-color-scheme" checked> Auto
  158. </label>
  159. <label>
  160. <input type="radio" value="dark" name="chosen-color-scheme"> Foncé
  161. </label>
  162. <label>
  163. <input type="radio" value="light" name="chosen-color-scheme"> Clair
  164. </label>
  165. </fieldset>
  166. </form>
  167. </template>
  168. </footer>
  169. <script src="/static/david/js/instantpage-5.1.0.min.js" type="module"></script>
  170. <script>
  171. function loadThemeForm(templateName) {
  172. const themeSelectorTemplate = document.querySelector(templateName)
  173. const form = themeSelectorTemplate.content.firstElementChild
  174. themeSelectorTemplate.replaceWith(form)
  175. form.addEventListener('change', (e) => {
  176. const chosenColorScheme = e.target.value
  177. localStorage.setItem('theme', chosenColorScheme)
  178. toggleTheme(chosenColorScheme)
  179. })
  180. const selectedTheme = localStorage.getItem('theme')
  181. if (selectedTheme && selectedTheme !== 'undefined') {
  182. form.querySelector(`[value="${selectedTheme}"]`).checked = true
  183. }
  184. }
  185. const prefersColorSchemeDark = '(prefers-color-scheme: dark)'
  186. window.addEventListener('load', () => {
  187. let hasDarkRules = false
  188. for (const styleSheet of Array.from(document.styleSheets)) {
  189. let mediaRules = []
  190. for (const cssRule of styleSheet.cssRules) {
  191. if (cssRule.type !== CSSRule.MEDIA_RULE) {
  192. continue
  193. }
  194. // WARNING: Safari does not have/supports `conditionText`.
  195. if (cssRule.conditionText) {
  196. if (cssRule.conditionText !== prefersColorSchemeDark) {
  197. continue
  198. }
  199. } else {
  200. if (cssRule.cssText.startsWith(prefersColorSchemeDark)) {
  201. continue
  202. }
  203. }
  204. mediaRules = mediaRules.concat(Array.from(cssRule.cssRules))
  205. }
  206. // WARNING: do not try to insert a Rule to a styleSheet you are
  207. // currently iterating on, otherwise the browser will be stuck
  208. // in a infinite loop…
  209. for (const mediaRule of mediaRules) {
  210. styleSheet.insertRule(mediaRule.cssText)
  211. hasDarkRules = true
  212. }
  213. }
  214. if (hasDarkRules) {
  215. loadThemeForm('#theme-selector')
  216. }
  217. })
  218. </script>
  219. </body>
  220. </html>