<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="https://www.aaron-gustafson.com/c/feed.min.css" ?><feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:amg="https://www.aaron-gustafson.com.com/amg-dtd/"><title>Aaron Gustafson: Content tagged progressive web apps</title><subtitle>The latest 20 posts and links tagged progressive web apps.</subtitle><id>https://www.aaron-gustafson.com</id><link href="https://www.aaron-gustafson.com/feeds/progressive-web-apps.xml" rel="self"/><link href="https://www.aaron-gustafson.com"/><author><name>Aaron Gustafson</name><uri>https://www.aaron-gustafson.com</uri></author><updated>2024-03-05T16:28:47Z</updated><entry><id>https://www.aaron-gustafson.com/notebook/links/safari-17-4-beta-release-notes/</id><title type="html"><![CDATA[🔗 Safari 17.4 Beta Release Notes]]></title><link href="https://www.aaron-gustafson.com/notebook/links/safari-17-4-beta-release-notes/" rel="alternate" type="text/html" /><link href="https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes" rel="related" type="text/html" /><published>2024-03-05T16:28:47Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Amidst all the kerfuffle over Apple’s push to drop PWAs (a.k.a., Home Screen Apps), two PWA features I worked on quietly landed in Safari for desktop: shortcuts &amp; categories.</p><blockquote><ul><li>Added support for the <code>shortcuts</code> manifest member on macOS. Shortcuts are available in the File menu and the Dock context menu. Users can set up custom keyboard shortcuts for them in System Settings &gt; Keyboard &gt; Keyboard Shortcuts &gt; App Shortcuts. (106137954)</li><li>Added support for the <code>categories</code> manifest member on macOS. When creating a Launchpad folder containing web apps, the folder is automatically named accordingly. (116480550)</li></ul></blockquote><p>🎉</p>]]></content><amg:twitter><![CDATA[Amidst all the kerfuffle over Apple’s push to drop PWAs (a.k.a., Home Screen Apps), two PWA features I worked on quietly landed in Safari for macOS: `shortcuts` & `categories`.]]></amg:twitter><category term="progressive web apps" /><category term="browsers" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/purepwa-a-radical-u-turn-in-web-development/</id><title type="html"><![CDATA[🔗 PurePWA — A Radical U-Turn in Web Development]]></title><link href="https://www.aaron-gustafson.com/notebook/links/purepwa-a-radical-u-turn-in-web-development/" rel="alternate" type="text/html" /><link href="https://medium.com/@neerventure/purepwa-a-radical-u-turn-in-web-development-a386c0dc092e" rel="related" type="text/html" /><published>2024-02-02T18:38:09Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I love experiments that showcase the power of the web platform and this is no exception: a pure web standards-powered Progressive Web App (PWA) replete with straightforward web components, view transitions, and more.</p>]]></content><amg:twitter><![CDATA[I love experiments that showcase the power of the web platform and this is no exception: a pure #WebStandards-powered #ProgressiveWebApp]]></amg:twitter><category term="progressive web apps" /><category term="HTML" /><category term="web components" /><category term="CSS" /><category term="JavaScript" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://miro.medium.com/v2/resize:fit:1200/1*ZEDw2ropDtOTGm_RUIMJlg.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/widgets/</id><title type="html"><![CDATA[✍🏻 Widgets!]]></title><link href="https://www.aaron-gustafson.com/notebook/widgets/" rel="alternate" type="text/html" /><published>2023-10-09T22:38:54Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p><h2 id="where-it-all-started" tabindex="-1"><a class="header-anchor" href="#where-it-all-started" aria-hidden="true">#</a> Where it all started</h2><p>I had <a href="https://web.archive.org/web/20200929174844/https://discourse.wicg.io/t/noodling-on-an-idea-projections-for-web-apps/3900">the original idea for “projections”</a> way back in 2019. Inspired by <a href="https://en.wikipedia.org/wiki/Dashboard_(macOS)#Widget_functions_and_capabilities">OS X’s Dashboard Widgets</a> and <a href="https://en.wikipedia.org/wiki/Adobe_AIR">Adobe AIR</a>, I’d begun to wonder if it might be possible to <em>project</em> a component from a website into those kinds of surfaces. Rather than building a bespoke widget that connected to an API, I thought it made sense to leverage an installed PWA to manage those “projections.” I shared the idea at TPAC that year and got some interest from a broad range of folks, but didn’t have much time to work on the details until a few years later.</p><p>In the intervening time, I kept working through the concept in my head. I mean in an ideal world, the widget would just be a responsive web page, right? But if that were the case, what happens when every widget loads the entirety of React to render their stock ticker? That seemed like a performance nightmare.</p><p>In my gut, I felt like the right way to build things would be to have a standard library of widget templates and to enable devs to flow data into them via a Service Worker. Alex Russell suggested I model the APIs on how Notifications are handled (since they serve a similar function) and I was off to the races.</p><p>I drafted <a href="https://github.com/aarongustafson/pwa-widgets">a substantial proposal for my vision of how PWA widgets should work</a>. Key aspects included:</p><ul><li>A declarative way to define and configure a widget from within the Web App Manifest;</li><li>A progressively enhanced pathway for devs to design a widget that adapts to its host environment, from using predefined templates to using custom templates to full-blown web-based widgets (with rendering akin to an <code>iframe</code>);</li><li>A collection of recommended stock templates that implementors should offer to support most widget types;</li><li>Extensibility to support custom templates using any of a variety of templating languages; and</li><li>A complete suite of tools for managing widgets and any associated business logic within a Service Worker.</li></ul><h2 id="widgets-became-a-reality" tabindex="-1"><a class="header-anchor" href="#widgets-became-a-reality" aria-hidden="true">#</a> Widgets became a reality</h2><p>After continuing to gently push on this idea with colleagues across Microsoft (and beyond), I discovered that the Windows 11 team was looking to open up the new Widget Dashboard to third-party applications. I saw this as an opportunity to turn my idea into a reality. After working my way into the conversation, I made a solid case for why PWAs needed to be a part of that story and… it worked! (It no doubt helped that companies including Meta, Twitter, and Hulu were all invested in PWA as a means of delivering apps for Windows.)</p><p>While the timeline for implementation didn’t allow us to tackle the entirety of my proposal, we did carve out the pieces that made for a compelling MVP. This allowed us to show what’s possible, see how folks use it, and plan for future investment in the space.</p><p>Sadly, it meant tabling two features I really loved:</p><ul><li><strong>Stock/predefined templates</strong>. A library of lightly theme-able, consistent, cross-platform templates based on common data structures (e.g., RSS/Atom, iCal) would make it incredibly simple for devs to build a widget. If implemented well, devs might not even need to write a single line of business logic in their Service Worker as the browser could pick up all of the configuration details from the Manifest.</li><li><strong>Configurable widget instances.</strong> Instead of singleton widgets, these would allow you to define a single widget type and replicate it for different use cases. For example, a widget to follow a social media user’s profile could be defined once and the individual instances could be configured with the specific account to be followed.</li></ul><p>I’m sincerely hopeful these two features eventually make their way to us as I think they truly unlock the power of the widget platform. Perhaps, with enough uptake on the current implementation, we can revisit these in the not-too-distant future.</p><hr><p>To test things out, I decided to build two widgets for this site:</p><ol><li>Latest posts</li><li>Latest links</li></ol><p>Both are largely the same in terms of their setup: They display a list of linked titles from this site.</p><h2 id="designing-my-widget-templates" tabindex="-1"><a class="header-anchor" href="#designing-my-widget-templates" aria-hidden="true">#</a> Designing my widget templates</h2><p>Given that they were going to be largely identical, I made <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">a single “feed” template for use in both widgets</a>. The templating tech I used is called <a href="https://adaptivecards.io">Adaptive Cards</a>, which is what Windows 11 uses for rendering.</p><p>Adaptive Card templates are relatively straightforward JSON:</p><pre class="language-json" tabindex="0"><code class="language-json">&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“AdaptiveCard”</span><span class="token punctuation">,</span><span class="token property">“$schema”</span><span class="token operator">:</span><span class="token string">“<a href="http://adaptivecards.io/schemas/adaptive-card.json">http://adaptivecards.io/schemas/adaptive-card.json</a>”</span><span class="token punctuation">,</span><span class="token property">“version”</span><span class="token operator">:</span><span class="token string">“1.6”</span><span class="token punctuation">,</span><span class="token property">“body”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“$data”</span><span class="token operator">:</span><span class="token string">“$&amp;#123;take(items,5)&amp;#125;”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“Container”</span><span class="token punctuation">,</span><span class="token property">“items”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“TextBlock”</span><span class="token punctuation">,</span><span class="token property">“text”</span><span class="token operator">:</span><span class="token string">“&amp;#91;$&amp;#123;title&amp;#125;&amp;#93;($&amp;#123;url&amp;#125;)”</span><span class="token punctuation">,</span><span class="token property">“wrap”</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token property">“weight”</span><span class="token operator">:</span><span class="token string">“Bolder”</span><span class="token punctuation">,</span><span class="token property">“spacing”</span><span class="token operator">:</span><span class="token string">“Padding”</span><span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“backgroundImage”</span><span class="token operator">:</span> &amp;#<span class="token number">123</span>;<span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://www.aaron-gustafson.com/i/background-logo.png">https://www.aaron-gustafson.com/i/background-logo.png</a>”</span><span class="token punctuation">,</span><span class="token property">“verticalAlignment”</span><span class="token operator">:</span><span class="token string">“Bottom”</span><span class="token punctuation">,</span><span class="token property">“horizontalAlignment”</span><span class="token operator">:</span><span class="token string">“Center”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">125</span>;</code></pre><p>What this structure does is:</p><ol><li>Create a container into which I will place the content;</li><li>Extract the first five <code>items</code> from the data being fed into the template (more on that in a moment);</li><li>Loop through each <code>item</code> and<ul><li>create a text block,</li><li>populate its content with Markdown to generate a linked title (using the <code>title</code> and <code>url</code> keys from the <code>item</code> object)</li><li>Set some basic styles to make the text bold, separate the titles a little and make them grow to fill the container; then, finally</li></ul></li><li>Set a background on the widget.</li></ol><p>The way Adaptive Cards work is that they flow JSON data into a template and render that. The variable names in the template map directly to the incoming data structure, so are totally up to you to define. As these particular widgets are feed-driven and <a href="https://www.aaron-gustafson.com/feeds/">this site already supports JSONFeed</a>, I set up the widgets to flow the appropriate feed into each and used the keys that were already there. For reference, here’s a sample JSONFeed <code>item</code>:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“id”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“title”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“summary”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“content_html”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“tags”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“date_published”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">}</span></code></pre><p>If you want to tinker with Adaptive Cards and make your own, you can do so with <a href="https://adaptivecards.io/designer/">their Designer tool</a>.</p><h3 id="defining-the-widgets-in-the-manifest" tabindex="-1"><a class="header-anchor" href="#defining-the-widgets-in-the-manifest" aria-hidden="true">#</a> Defining the widgets in the Manifest</h3><p>With a basic template created, the next step was to set up the two widgets in my Manifest. As they both function largely the same, I’ll just focus on the definition for one of them.</p><p>First off, defining widgets in the Manifest is done via the <code>widgets</code> member, which is an array (much like <code>icons</code> and <code>shortcuts</code>). Each widget is represented as an object in that array. Here is the definition for the “latest posts” widget:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Latest Posts”</span><span class="token punctuation">,</span><span class="token property">“short_name”</span><span class="token operator">:</span><span class="token string">“Posts”</span><span class="token punctuation">,</span><span class="token property">“tag”</span><span class="token operator">:</span><span class="token string">“feed-posts”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“The latest posts from Aaron Gustafson’s blog”</span><span class="token punctuation">,</span><span class="token property">“template”</span><span class="token operator">:</span><span class="token string">“feed”</span><span class="token punctuation">,</span><span class="token property">“ms_ac_template”</span><span class="token operator">:</span><span class="token string">“/w/feed.ac.json”</span><span class="token punctuation">,</span><span class="token property">“data”</span><span class="token operator">:</span><span class="token string">“/feeds/latest-posts.json”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“application/json”</span><span class="token punctuation">,</span><span class="token property">“auth”</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token property">“update”</span><span class="token operator">:</span><span class="token number">21600</span><span class="token punctuation">,</span><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icons/webicon-rss.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“120x120”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“screenshots”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/screenshots/widget-posts.png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“387x387”</span><span class="token punctuation">,</span><span class="token property">“label”</span><span class="token operator">:</span><span class="token string">“The latest posts widget”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></code></pre><p>Breaking this down:</p><ol><li><code>name</code> and <code>short_name</code> act much like these keys in the root of the Manifest as well as in <code>shortcuts</code>: The <code>name</code> value is used as the name for the widget unless there’s not enough room, in which case <code>short_name</code> is used.</li><li>You can think of <code>tag</code> as analogous to <code>class</code> in HTML sense. It’s a way of labeling a widget so you can easily reference it later. Each widget instance will have a unique id created by the widget service, but that instance (or all instances, if the widget supports multiple instances) can be accessed via the <code>tag</code>. But more on that later.</li><li>The <code>description</code> key is used for marketing the widget within a host OS or digital storefront. It should accurately (and briefly) describe what the widget does.</li><li>The <code>template</code> key is not currently used in the Windows 11 implementation but refers to the expected standard library widget template provided by the system. As a template library is not currently available, the <code>ms_ac_template</code> value is used to provide a URL to get the custom Adaptive Card (hence “ac”) template. The “ms_” prefix is there because it’s expected that this would be a Microsoft-proprietary property. It follows <a href="https://www.w3.org/TR/appmanifest/#proprietary-extensions">the guidance for extending the Manifest</a>.</li><li>The <code>data</code> and <code>type</code> keys define the path to the data that should be fed into the template for rendering by the widget host and the MIME of the data format it’s in. The Windows 11 implementation currently only accepts JSON data, but the design of widgets is set up to allow for this to eventually extend to other standardized formats like RSS, iCal, vCard, and such.</li><li><code>update</code> is an optional configuration member allowing you to set how often you’d like the widget to update, in seconds. Developers currently need to add the logic for implementing this into their Service Worker, but this setup allows the configuration to remain independent of the JavaScript code, making it easier to maintain.</li><li>Finally, <code>icons</code> and <code>screenshots</code> allow us to define how the widget shows up in the widget host and how it is promoted for install.</li></ol><p>When someone installs my site as a PWA, the information about the available widgets gets ingested by the browser. The browser then determines, based on the provided values and its knowledge of the available widget service(s) on the device, which widgets should be offered. On Windows 11, this information is <a href="https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-cs#update-the-package-manifest">routed into the AppXManifest that governs how apps are represented in Windows</a>. The Windows 11 widget service can then read in the details about the available widgets and offer them for users to install.</p><figure id="2023-10-09-01"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-promotion.gif" alt=""></p><figcaption>An animated capture of Windows 11’s widget promotion surface, showing 2 widgets available from this site’s PWA.</figcaption></figure><h2 id="adding-widget-support-to-my-service-worker" tabindex="-1"><a class="header-anchor" href="#adding-widget-support-to-my-service-worker" aria-hidden="true">#</a> Adding widget support to my Service Worker</h2><p>As I mentioned earlier, all of the plumbing for widgets is done within a Service Worker and is modeled on the Notifications API. I’m not going to exhaustively detail how it all works, but I’ll give you enough detail to get you started.</p><p>First off, widgets are exposed via the <code>self.widgets</code> interface. Most importantly, this interface lets you access and update any instances of a widget connected to your PWA.</p><h3 id="installing-a-widget" tabindex="-1"><a class="header-anchor" href="#installing-a-widget" aria-hidden="true">#</a> Installing a widget</h3><p>When a user chooses to install a widget, that emits a “widgetinstall” event in your Service Worker. You use that to kickoff the widget lifecycle by gathering the template and data needed to instantiate the widget:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetinstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Installing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>The event argument comes in with details of the specific widget being instantiated (as <code>event.widget</code>). In the code above, you can see I’ve logged the widget’s <code>tag</code> value to the console. I pass the widget information over to my <code>initializeWidget()</code> function and it updates the widget with the latest data and, if necessary, sets up a <a href="https://developer.mozilla.org/docs/Web/API/Web_Periodic_Background_Synchronization_API">Periodic Background Sync</a>:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">await</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>The code for my <code>updateWidget()</code> function is as follows:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">updateWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">const</span> template <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>msAcTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> data <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>widgets<span class="token punctuation">.</span><span class="token function">updateByTag</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span> template<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Couldn’t update the widget &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function does the following:</p><ol><li>Get the template for this widget</li><li>Get the data to flow into the template</li><li>Use the <code>self.widgets.updateByTag()</code> method to push the <var>template</var> and <var>data</var> to the widget service to update any widget instances connected to the widget’s <code>tag</code>.</li></ol><p>As I mentioned, I also have code in place to take advantage of Periodic Background Sync if/when it’s available and the browser allows my site to do it:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> tag <span class="token operator">=</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">getTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tags</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token comment">// only one registration per tag</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>tags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>periodicSync<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">minInterval</span><span class="token operator">:</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>update<span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function also receives the widget details and:</p><ol><li>Looks to see if the widget <code>definition</code> (from the Manifest) includes an <code>update</code> member. If it has one, it…</li><li>Checks to see if there’s already a Periodic Background Sync that is registered for this tag. If none exists, it…</li><li>Registers a new Periodic Background Sync using the <code>tag</code> value and a minimum interval equal to the <code>update</code> requested.</li></ol><p>The <code>update</code> member, as you may recall, is the frequency (in seconds) you’d ideally like the widget to be updated. In reality, you’re at the mercy of the browser as to when (or even if) your sync will run, but that’s totally cool as there are other ways to update widgets as well.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><h3 id="uninstalling-a-widget" tabindex="-1"><a class="header-anchor" href="#uninstalling-a-widget" aria-hidden="true">#</a> Uninstalling a widget</h3><p>When a user uninstalls a widget, your Service Worker will receive a “widgetuninstall” event. Much like the “widgetinstall” event, the argument contains details about that widget which you can use to clean up after yourself:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetuninstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Uninstalling &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Your application may have different cleanup needs, but this is a great time to clean up any unneeded Periodic Sync registrations. Just be sure to check the length of the widget’s <code>instances</code> array (<code>widget.instances</code>) to make sure you’re dealing with the last instance of a given widget <em>before</em> you unregister the sync:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>instances<span class="token punctuation">.</span>length <span class="token operator">===</span><span class="token number">1</span><span class="token operator">&amp;&amp;</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">unregister</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="refreshing-your-widgets" tabindex="-1"><a class="header-anchor" href="#refreshing-your-widgets" aria-hidden="true">#</a> Refreshing your widgets</h3><p>Widget platforms may periodically freeze your widget(s) to save resources. For example, they may do this when widgets are not visible. To keep your widgets up to date, they will periodically issue a “widgetresume” event. If you’ve modeled your approach on the one I’ve outlined above, you can route this event right through to your <code>updateWidget()</code> function:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetresume”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Resuming &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="actions" tabindex="-1"><a class="header-anchor" href="#actions" aria-hidden="true">#</a> Actions</h3><p>While I don’t want to get too into the weeds here, I do want to mention that widgets can have predefined user actions as well. These actions result in “widget click” events being sent back to the Service Worker so you can respond to them:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetclick”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">const</span> widget <span class="token operator">=</span> event<span class="token punctuation">.</span>widget<span class="token punctuation">;</span><span class="token keyword">const</span> action <span class="token operator">=</span> event<span class="token punctuation">.</span>action<span class="token punctuation">;</span><span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// Custom Actions</span><span class="token keyword">case</span><span class="token string">“refresh”</span><span class="token operator">:</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>For a great example of how a widget can integrate actions, you should check out <a href="https://microsoftedge.github.io/Demos/pwamp/">the demo PWAmp project</a>. <a href="https://github.com/MicrosoftEdge/Demos/blob/main/pwamp/sw-widgets.js">Their Service Worker widget code</a> is worth a read.</p><h2 id="result!" tabindex="-1"><a class="header-anchor" href="#result!" aria-hidden="true">#</a> Result!</h2><p>With all of these pieces in place, I was excited to see my site showing up in the Widget Dashboard in Windows 11.</p><figure id="2023-10-09-02"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-in-windows.jpg" alt=""></p><figcaption>A screenshot of Windows 11 showing the Widget Dashboard overlaying the desktop with this site installed as a PWA to the right. The “latest posts” and “latest links” widgets are shown.</figcaption></figure><p>You can view the full source code on GitHub:</p><ul><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">“Feed” Adaptive Card Template</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/manifest.json#L158-L237">Widget definitions in the Manifest</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/_javascript/serviceworker/widgets.js">Widgets code in my Service Worker</a></li></ul><hr><p>I’m quite hopeful this will be the first of many places PWA-driven widgets will appear. If you’s like to see them supported elsewhere, be sure to tell your browser and OS vendor(s) of choice. The more they hear from their user base that this feature is needed, the more likely we are to see it get implemented in more places.</p><h2 id="addendum%3A-gotchas" tabindex="-1"><a class="header-anchor" href="#addendum%3A-gotchas" aria-hidden="true">#</a> Addendum: Gotchas</h2><p>In wiring this all up, I ran into a few current bugs I wanted to flag so you can avoid them:</p><ul><li>The <code>icons</code> member won’t accept SVG images. This should eventually be fixed, but it was keeping my widgets from appearing as installable.</li><li>The <code>screenshots</code> members can’t be incredibly large. I’m told you should provide square screenshots no larger than 500px ×500px.</li></ul><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Have you checked out <a href="https://developer.mozilla.org/docs/Web/API/Server-sent_events/Using_server-sent_events">Server Events</a>? <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:twitter><amg:summary><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:summary><summary type="html"><![CDATA[<p>I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!</p>]]></summary><category term="progressive web apps" /><category term="experiments" /><category term="JavaScript" /><category term="Microsoft" /><category term="this site" /><category term="user experience" /><category term="web development" /><category term="web standards" /><category term="Windows" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2023-10-09/hero1.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2023-08-28-building-resilient-web-apps-progressive-enhancement-unveiled/</id><title type="html"><![CDATA[🎧 Building Resilient Web Apps: Progressive Enhancement Unveiled]]></title><link href="https://www.youtube.com/watch?v=-ZqsLwWM7YE" rel="alternate" type="text/html" /><published>2023-08-28T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Let’s delve into the world of progressive enhancement for web applications. From dissecting common pitfalls to offering invaluable tips, we unravel the art of building robust and inclusive web experiences.</p>]]></content><category term="progressive enhancement" /><category term="accessibility" /><category term="JavaScript" /><category term="progressive web apps" /><category term="web design" /><category term="web development" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2023-07-26-logrocket-2/</id><title type="html"><![CDATA[🎧 Aaron Gustafson talks progressive enhancement, PWAs, AI, and accessibility]]></title><link href="https://podrocket.logrocket.com/progressive-enhancement-pwa-ai-accessibility" rel="alternate" type="text/html" /><published>2023-07-26T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I rejoined the LogRocket folks to talk about a bunch of the topics I’m currently thinking about.</p>]]></content><category term="progressive enhancement" /><category term="progressive web apps" /><category term="AI/ML" /><category term="accessibility" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/-news-from-wwdc23-webkit-features-in-safari-17-beta/</id><title type="html"><![CDATA[🔗 News from WWDC23: WebKit Features in Safari 17 beta]]></title><link href="https://www.aaron-gustafson.com/notebook/links/-news-from-wwdc23-webkit-features-in-safari-17-beta/" rel="alternate" type="text/html" /><link href="https://webkit.org/blog/14205/news-from-wwdc23-webkit-features-in-safari-17-beta/" rel="related" type="text/html" /><published>2023-06-07T20:35:53Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I’m very excited to see Apple roll out greater support for PWAs (though I’d bet good money on them never using that term publicly) in macOS Safari! I sincerely hope this is the beginning of many good things to come.</p>]]></content><amg:twitter><![CDATA[I’m very excited to see Apple roll out greater support for #PWAs in macOS Safari! ]]></amg:twitter><category term="progressive web apps" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://webkit.org/wp-content/uploads/Whats-new-in-web-apps-1024x663.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/pwa-for-beginners/</id><title type="html"><![CDATA[🔗 PWA for Beginners]]></title><link href="https://www.aaron-gustafson.com/notebook/links/pwa-for-beginners/" rel="alternate" type="text/html" /><link href="https://learn.microsoft.com/en-us/shows/pwa-for-beginners/" rel="related" type="text/html" /><published>2023-03-08T17:37:08Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Some of my colleagues at Microsoft have put together a 17-part video learning course on Progressive Web Apps. It’s got a ton of great material if you’re looking to get started.</p>]]></content><amg:twitter><![CDATA[New video course on #ProgressiveWebApps from some of the folks that brought you #30DaysofPWA. Awesome stuff!]]></amg:twitter><category term="progressive web apps" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://learn.microsoft.com/en-us/media/logos/logo-ms-social.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/safari-16-4-beta-release-notes/</id><title type="html"><![CDATA[🔗 Safari 16.4 Beta Release Notes]]></title><link href="https://www.aaron-gustafson.com/notebook/links/safari-16-4-beta-release-notes/" rel="alternate" type="text/html" /><link href="https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes" rel="related" type="text/html" /><published>2023-02-17T00:18:37Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>So much goodness in this release!</p><ul><li>Scroll to Text Fragment</li><li>Service Workers and Shared Workers can access the Permissions API.</li><li>Notification API in dedicated workers.</li><li>Reporting API.</li><li>Screen Orientation API.</li><li>Screen Wake Lock API.</li><li>unprefixed Fullscreen API</li><li>Push in web apps saved to the home screen on iOS</li><li>“id” member in Web App Manifest</li><li>Badging API.</li><li>third-party browsers can Add to Home Screen</li></ul>]]></content><amg:twitter><![CDATA[Awesome Safari release! 👏🏻👏🏻👏🏻]]></amg:twitter><category term="browsers" /><category term="progressive web apps" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://docs.developer.apple.com/tutorials/developer-og.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/rebuilding-a-php-app-using-isomorphic-javascript-with-eleventy-and-netlify/</id><title type="html"><![CDATA[✍🏻 Rebuilding a PHP App using Isomorphic JavaScript with Eleventy & Netlify]]></title><link href="https://www.aaron-gustafson.com/notebook/rebuilding-a-php-app-using-isomorphic-javascript-with-eleventy-and-netlify/" rel="alternate" type="text/html" /><published>2023-02-03T20:40:59Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Back in the early days of the iPhone, I created <a href="https://tipr.mobi">Tipr</a>, a tip calculator that always produces a palindrome total.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> This is an overview of the minimal work I did to make it a modern web app that can run without a traditional back-end.</p><h2 id="what-i-had-to-work-with" tabindex="-1"><a class="header-anchor" href="#what-i-had-to-work-with" aria-hidden="true">#</a> What I had to work with</h2><p>The previous iteration of Tipr was built in my hotel room while I was on site doing some consulting for a certain Silicon Valley company. I was rocking a <a href="https://wikipedia.org/wiki/Treo_650">Palm Treo 650</a> at the time and that day a few of my colleagues had lined up to wait for the release of <a href="https://wikipedia.org/wiki/IPhone_(1st_generation)">the very first iPhone</a>. At the time, web apps were the only way to get an “app” on the iPhone as there was no SDK or even an App Store.</p><figure id="2023-02-02-02"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-02-03/iphone.jpg" alt="" width="696" height="928"></p><figcaption>Tipr on the 1st generation iPhone, in the hands of Micah Alpern, June 2007.</figcaption></figure><p>I did a lot of PHP development back in the day, so armed with all of the mobile web development best practices of the day, I set about building the site and making it speedy. Some of the notable features of Tipr included:</p><ul><li>Inlining CSS &amp; JS file contents into the HTML.</li><li>Using <a href="https://www.php.net/manual/en/book.outcontrol.php">PHP output buffers</a> to compress the HTML on the server before sending it over the wire.</li><li>Server side processing in PHP.</li><li>Client side processing via <abbr aria-label="XMLHttpRequest" title="XMLHttpRequest">XHR</abbr> to a PHP-driven API.</li></ul><p>At the time, most of these approaches were very new. As an industry, we weren’t doing a whole lot to ensure peak performance on mobile because most people’s mobile browsers were pretty crappy. This was the heyday of Usablenet’s “mobile friendly” bolt-on and WAP. Then came Mobile Safari.</p><figure id="2023-02-02-03"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-02-03/app-store.png" alt="" width="696" height="472"></p><figcaption>Tipr in the original Apple App Store, back when web apps were first class citizens on iPhone OS.</figcaption></figure><h2 id="a-lot-has-changed-since-2007" tabindex="-1"><a class="header-anchor" href="#a-lot-has-changed-since-2007" aria-hidden="true">#</a> A lot has changed since 2007</h2><p>The Tipr site has remained largely untouched since I built it in the Summer of 2007. That October, I added a theme switcher that made the site <a href="https://pinkforoctober.org/">pink for October</a> (Breast Cancer Awareness Month). I added a free text message-based interface using the then-free <a href="https://www.textmarks.com/">TextMarks</a> service and <a href="https://twitter.com/tipr">a Twitter bot</a> as well. But as far as the web interface went, it remained largely untouched.</p><p>Here are a handful of things that have come to the web in the intervening years:</p><ul><li>HTML5 Form Validation API</li><li>SVG support</li><li>CSS3</li><li>Media Queries</li><li>Web App Manifest</li><li>Service Worker (and its precursor the AppCache)</li><li>Flexbox</li><li>CSS Grid</li></ul><p>Phew, that’s a lot! While I haven’t made upgrades in all these areas, I did sprinkle in a few, mainly to make it a true PWA and boost performance.</p><h2 id="moving-from-php-to-a-%E2%80%9Cstatic%E2%80%9D-site" tabindex="-1"><a class="header-anchor" href="#moving-from-php-to-a-%E2%80%9Cstatic%E2%80%9D-site" aria-hidden="true">#</a> Moving from PHP to a “static” site</h2><p>Much of my work over the last few years has been in the world of static site generators (e.g., <a href="https://jekyllrb.com/">Jekyll</a>, <a href="https://www.11ty.dev/">Eleventy</a>). I’m quite enamored of <a href="https://www.11ty.dev/">Eleventy</a>, having used it for a number of projects at this point. Since I know it really well, it made sense to use it for this project too. The installation steps are minimal and I already had a library of configuration options, plugins, and filters to roll with.</p><p>While in the process of migrating to Eleventy, I also took the opportunity to</p><ul><li>Swap raster graphics for SVGs,</li><li>Set up a Web App Manifest,</li><li>Add a Service Worker, and</li><li>Update the site’s <code>meta</code> info to reflect current best practices.</li></ul><p>I also swapped out the PHP logic that governed the pink color theme for <a href="https://github.com/aarongustafson/tipr.mobi/blob/main/src/_includes/layouts/base.njk#L7-L12">a simple <code>script</code> in the <code>head</code> of the every page</a>. Since the color change is an enhancement, rather than a necessity, I didn’t feel like it was something I needed to manage another way.</p><p>The greatest challenge in moving Tipr over to a static site was setting up the tip calculation engine, which had been in PHP to ensure it would work even if JavaScript wasn’t available.</p><h2 id="migrating-the-core-logic-to-isomorphic-javascript" tabindex="-1"><a class="header-anchor" href="#migrating-the-core-logic-to-isomorphic-javascript" aria-hidden="true">#</a> Migrating the core logic to isomorphic JavaScript</h2><p>When I originally built Tipr, JavaScript on the back-end wasn’t a thing. That’s why the core tip calculation engine was built in PHP. At the time, even <abbr aria-label="XMLHttpRequest" title="XMLHttpRequest">XHR</abbr> was in its infancy, so the fact that I could use PHP to do the calculations for both the server-side—for when JavaScript wasn’t available—and client-side—when it was—was pretty amazing.</p><p>Today, JavaScript is ubiquitous across the whole stack, which made it the logical choice for building out the revised tip calculator. As with the original, I needed the calculation to work on the client side if it could—saving a round trip to the server—but to also have the ability to fall back to a traditional form submission if the client-side approach wasn’t feasible. That would be possible by having client-side JavaScript for the form itself, with the server-side piece handled by <a href="https://docs.netlify.com/edge-functions/overview/">Netlify’s Edge Functions</a> (integrated through <a href="https://www.11ty.dev/blog/eleventy-edge/">Eleventy’s Edge plugin</a>).</p><p>From an architectural standpoint, I really didn’t want to have my logic duplicated in each place, so I began to play around with ensconcing the calculation logic in a JavaScript include, so I could import it into the form page itself <em>and</em> a JavaScript module that the Edge Function could use.</p><p>You can view <a href="https://github.com/aarongustafson/tipr.mobi">Tipr’s source on GitHub</a>, but here’s a basic rundown of the relevant directories and files:</p><pre class="language-txt" tabindex="0"><code class="language-txt">netlifyedge-functionstipr.jssrc_includesjstipr.jsjprocess.njkindex.htmlnetlify.toml</code></pre><h3 id="src%2F_includes%2Fjs%2Ftipr.js" tabindex="-1"><a class="header-anchor" href="#src%2F_includes%2Fjs%2Ftipr.js" aria-hidden="true">#</a><code>src/_includes/js/tipr.js</code></h3><p>This file contains the central logic of the tip calculator. It’s written in vanilla JavaScript with the intent that it would be understandable by the widest possible assortment of browsers out there.</p><h3 id="src%2Findex.html" tabindex="-1"><a class="header-anchor" href="#src%2Findex.html" aria-hidden="true">#</a><code>src/index.html</code></h3><p>The homepage of the site is also home to the tip calculation form. Below the form is an embedded <code>script</code> element containing the logic for interacting with the DOM for the client-side version of the tip calculator. I include the logic at the top of that <code>script</code>:</p><pre class="language-njk" tabindex="0"><code class="language-njk"><span class="token operator">&lt;</span><span class="token variable">script</span><span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">include</span><span class="token string">“js/tipr.js”</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token operator">//</span><span class="token variable">The</span><span class="token variable">rest</span><span class="token variable">of</span><span class="token variable">the</span><span class="token variable">JavaScript</span><span class="token variable">logic</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">script</span><span class="token operator">&gt;</span></code></pre><h3 id="src%2Fj%2Fprocess.njk" tabindex="-1"><a class="header-anchor" href="#src%2Fj%2Fprocess.njk" aria-hidden="true">#</a><code>src/j/process.njk</code></h3><p>This file exists solely to export the JavaScript logic from the include in a way that it can be consumed by the Edge Function. It will render a new JavaScript file called “process.js” and turns the central processing logic into a JavaScript module that <a href="https://deno.land/">Deno</a> can use (Deno powers Netlify’s Edge Functions):</p><pre class="language-njk" tabindex="0"><code class="language-njk"><span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token variable">layout</span><span class="token punctuation">:</span><span class="token boolean">false</span><span class="token variable">permalink</span><span class="token punctuation">:</span><span class="token operator">/</span><span class="token variable">j</span><span class="token operator">/</span><span class="token variable">process</span><span class="token punctuation">.</span><span class="token variable">js</span><span class="token operator">-</span><span class="token operator">-</span><span class="token operator">-</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">include</span><span class="token string">“js/tipr.js”</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token variable">export</span><span class="token punctuation">{</span><span class="token variable">process</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><h3 id="netlify%2Fedge-functions%2Ftipr.js" tabindex="-1"><a class="header-anchor" href="#netlify%2Fedge-functions%2Ftipr.js" aria-hidden="true">#</a><code>netlify/edge-functions/tipr.js</code></h3><p>We define Edge Functions for use with Netlify in the <code>netlify/edge-functions</code> folder. To make use of the core JavaScript logic in the Edge Function, I can import it from the module created above before using it in the function itself:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">import</span><span class="token punctuation">{</span> process <span class="token punctuation">}</span><span class="token keyword">from</span><span class="token string">“./…/…/_site/j/process.js”</span><span class="token punctuation">;</span><span class="token keyword">function</span><span class="token function">setCookie</span><span class="token punctuation">(</span><span class="token parameter">context<span class="token punctuation">,</span> name<span class="token punctuation">,</span> value</span><span class="token punctuation">)</span><span class="token punctuation">{</span>context<span class="token punctuation">.</span>cookies<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token punctuation">{</span>name<span class="token punctuation">,</span>value<span class="token punctuation">,</span><span class="token literal-property property">path</span><span class="token operator">:</span><span class="token string">“/”</span><span class="token punctuation">,</span><span class="token literal-property property">httpOnly</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token literal-property property">secure</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token literal-property property">sameSite</span><span class="token operator">:</span><span class="token string">“Lax”</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">export</span><span class="token keyword">default</span><span class="token keyword">async</span><span class="token punctuation">(</span><span class="token parameter">request<span class="token punctuation">,</span> context</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">let</span> url <span class="token operator">=</span><span class="token keyword">new</span><span class="token class-name">URL</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">// Save to cookie, redirect back to form</span><span class="token keyword">if</span><span class="token punctuation">(</span>url<span class="token punctuation">.</span>pathname <span class="token operator">===</span><span class="token string">“/process/”</span><span class="token operator">&amp;&amp;</span> request<span class="token punctuation">.</span>method <span class="token operator">===</span><span class="token string">“POST”</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">“content-type”</span><span class="token punctuation">)</span><span class="token operator">===</span><span class="token string">“application/x-www-form-urlencoded”</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> body <span class="token operator">=</span><span class="token keyword">await</span> request<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">formData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">let</span> postData <span class="token operator">=</span> Object<span class="token punctuation">.</span><span class="token function">fromEntries</span><span class="token punctuation">(</span>body<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">let</span> result <span class="token operator">=</span><span class="token function">process</span><span class="token punctuation">(</span> postData<span class="token punctuation">.</span>check<span class="token punctuation">,</span> postData<span class="token punctuation">.</span>percent <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setCookie</span><span class="token punctuation">(</span> context<span class="token punctuation">,</span><span class="token string">“check”</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>check <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setCookie</span><span class="token punctuation">(</span> context<span class="token punctuation">,</span><span class="token string">“tip”</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>tip <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token function">setCookie</span><span class="token punctuation">(</span> context<span class="token punctuation">,</span><span class="token string">“total”</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span>total <span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span><span class="token keyword">new</span><span class="token class-name">Response</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">status</span><span class="token operator">:</span><span class="token number">302</span><span class="token punctuation">,</span><span class="token literal-property property">headers</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token literal-property property">location</span><span class="token operator">:</span><span class="token string">“/results/”</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token keyword">return</span> context<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><p>What’s happening here is that when a request comes in to this Edge Function, the default export will be executed. Most of this code is directly lifted from <a href="https://edge-functions-examples.netlify.app/">Netlify’s Edge Functions demo site</a>. I grab the form data, pass it into the <code>process</code> function, and then set browser cookies for each of the returned values before redirecting the request to <a href="https://github.com/aarongustafson/tipr.mobi/blob/main/src/results.html">the result page</a>.</p><p>On that page, I use Eleventy’s Edge plugin to render the check, tip, and total amounts:</p><pre class="language-njk" tabindex="0"><code class="language-njk"><span class="token delimiter punctuation">{%</span><span class="token tag keyword">edge</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">set</span><span class="token variable">check</span><span class="token operator">=</span><span class="token variable">eleventy</span><span class="token punctuation">.</span><span class="token variable">edge</span><span class="token punctuation">.</span><span class="token variable">cookies</span><span class="token punctuation">.</span><span class="token variable">check</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">set</span><span class="token variable">tip</span><span class="token operator">=</span><span class="token variable">eleventy</span><span class="token punctuation">.</span><span class="token variable">edge</span><span class="token punctuation">.</span><span class="token variable">cookies</span><span class="token punctuation">.</span><span class="token variable">tip</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">set</span><span class="token variable">total</span><span class="token operator">=</span><span class="token variable">eleventy</span><span class="token punctuation">.</span><span class="token variable">edge</span><span class="token punctuation">.</span><span class="token variable">cookies</span><span class="token punctuation">.</span><span class="token variable">total</span><span class="token operator">%</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token variable">tr</span><span class="token variable">id</span><span class="token operator">=</span><span class="token string">“check”</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">th</span><span class="token variable">scope</span><span class="token operator">=</span><span class="token string">“row”</span><span class="token operator">&gt;</span><span class="token variable">Check</span><span class="token operator">&amp;</span><span class="token variable">nbsp</span><span class="token punctuation">;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">th</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">td</span><span class="token operator">&gt;</span>$<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token variable">check</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">td</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">tr</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">tr</span><span class="token variable">id</span><span class="token operator">=</span><span class="token string">“tip”</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">th</span><span class="token variable">scope</span><span class="token operator">=</span><span class="token string">“row”</span><span class="token operator">&gt;</span><span class="token variable">Tip</span><span class="token operator">&amp;</span><span class="token variable">nbsp</span><span class="token punctuation">;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">th</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">td</span><span class="token operator">&gt;</span>$<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token variable">tip</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">td</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">tr</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">tr</span><span class="token variable">id</span><span class="token operator">=</span><span class="token string">“total”</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">th</span><span class="token variable">scope</span><span class="token operator">=</span><span class="token string">“row”</span><span class="token operator">&gt;</span><span class="token variable">Total</span><span class="token operator">&amp;</span><span class="token variable">nbsp</span><span class="token punctuation">;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">th</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token variable">td</span><span class="token operator">&gt;</span>$<span class="token punctuation">{</span><span class="token punctuation">{</span><span class="token variable">total</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">td</span><span class="token operator">&gt;</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token variable">tr</span><span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token operator">%</span><span class="token variable">endedge</span><span class="token operator">%</span><span class="token punctuation">}</span></code></pre><p>Side note: The cookies get reset using <a href="https://github.com/aarongustafson/tipr.mobi/blob/main/netlify/edge-functions/reset.js">a separate Edge Function</a>.</p><h3 id="netlify.toml" tabindex="-1"><a class="header-anchor" href="#netlify.toml" aria-hidden="true">#</a><code>netlify.toml</code></h3><p>To wire up the Edge Functions, we use put a <code>netlify.toml</code> file in the root of the project. Configuration is pretty straightforward: you tell it the Edge Function you want to use and the path to associate it with. You can choose to associate it with a unique path or run the Edge Function on every path.</p><p>Here’s an excerpt from <a href="https://github.com/aarongustafson/tipr.mobi/blob/main/netlify.toml">Tipr’s <code>netlify.toml</code></a> as it pertains to the Edge Function above:</p><pre class="language-toml" tabindex="0"><code class="language-toml"><span class="token punctuation">[</span><span class="token punctuation">[</span><span class="token table class-name">edge_functions</span><span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token key property">function</span><span class="token punctuation">=</span><span class="token string">“tipr”</span><span class="token key property">path</span><span class="token punctuation">=</span><span class="token string">“/process/”</span></code></pre><p>This tells Netlify to route requests to <code>/process/</code> through <code>netlify/edge-functions/tipr.js</code>. Then all that was left to do was wire up the <code>form</code> to use that endpoint as its <code>action</code>:</p><pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>form</span><span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>calc<span class="token punctuation">”</span></span><span class="token attr-name">method</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>post<span class="token punctuation">”</span></span><span class="token attr-name">action</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>/process/<span class="token punctuation">”</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>form</span><span class="token punctuation">&gt;</span></span></code></pre><h3 id="isomorphic-edges" tabindex="-1"><a class="header-anchor" href="#isomorphic-edges" aria-hidden="true">#</a> Isomorphic Edges</h3><p>It took a fair bit of time to figure this all out, but I’m pretty excited by the possibilities of this approach for building more static isomorphic apps. Oh, and the new site… <a href="https://speedlify.aaron-gustafson.com/apps/">is fast</a>.</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Why a palindrome? Well, it makes it pretty easy to detect tip fraud because all restaurant totals will always be the same forwards &amp; backwards. It’s a little easier than a checksum. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[Wherein I migrate @tipr from a #PHP-based mobile web app to a #serverless #PWA.]]></amg:twitter><amg:summary><![CDATA[Back in the early days of the iPhone, I created tipr.mobi, a tip calculator that always produces a palindrome total. This is an overview of the minimal work I did to make it a modern web app that can run without a traditional back-end.]]></amg:summary><summary type="html"><![CDATA[<p>Back in the early days of the iPhone, I created tipr.mobi, a tip calculator that always produces a palindrome total. This is an overview of the minimal work I did to make it a modern web app that can run without a traditional back-end.</p>]]></summary><category term="mobile" /><category term="progressive enhancement" /><category term="progressive web apps" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2023-02-03/hero.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/pwas-in-2022/</id><title type="html"><![CDATA[🔗 PWAs in 2022]]></title><link href="https://www.aaron-gustafson.com/notebook/links/pwas-in-2022/" rel="alternate" type="text/html" /><link href="https://almanac.httparchive.org/en/2022/pwa" rel="related" type="text/html" /><published>2022-10-13T16:51:28Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Curious to know what’s going on with Progressive Web Apps in 2022? The 2022 edition of the Web Almanac is out, with an excellent chapter on PWAs by Diego Gonzalez and Beth Pan.</p>]]></content><amg:twitter><![CDATA[Excellent overview of the state of #PWAs on the web in 2022, from @diekus, @beth_panx, and many others.]]></amg:twitter><category term="progressive web apps" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://almanac.httparchive.org/static/images/2019/pwa/hero_lg.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/speaking-engagements/dont-worry-be-appy/</id><title type="html"><![CDATA[📢 Don’t Worry, Be Appy]]></title><link href="" rel="alternate" type="text/html" /><published>2022-04-18T08:09:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>When it comes delivering software that deeply integrates with operating systems, the web has historically struggled to compete with compiled apps. Many frameworks have sought to bridge this chasm over the years, but they have become less necessary as the web continues to evolve deeper connections with operating systems.</p><p>In this talk, Aaron Gustafson walks you through a host of new web platform features you can use today to grant your web projects app-like super powers. Learn the simple steps you can take to improve how users discover and install your software offerings.</p>]]></content><amg:twitter><![CDATA[In this talk, I walk you through a host of new web platform features you can use today to grant your web projects app-like super powers.]]></amg:twitter><amg:summary><![CDATA[In this talk, I walk you through a host of new web platform features you can use today to grant your web projects app-like super powers.]]></amg:summary><summary type="html"><![CDATA[<p>In this talk, I walk you through a host of new web platform features you can use today to grant your web projects app-like super powers.</p>]]></summary><category term="progressive web apps" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/undefined" /></entry><entry><id>https://www.aaron-gustafson.com/publications/articles/30daysofpwa-caching-your-app-data/</id><title type="html"><![CDATA[📄 #30DaysOfPWA: Caching Your App Data]]></title><link href="https://dev.to/azure/12-caching-your-app-data-3f0j" rel="alternate" type="text/html" /><published>2022-02-25T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This brief article will walk you through how to be more effective and organized when it comes to caching.</p>]]></content><category term="progressive web apps" /></entry><entry><id>https://www.aaron-gustafson.com/publications/articles/30daysofpwa-creating-application-shortcuts/</id><title type="html"><![CDATA[📄 #30DaysOfPWA: Creating Application Shortcuts]]></title><link href="https://dev.to/azure/09-creating-application-shortcuts-m0i" rel="alternate" type="text/html" /><published>2022-02-22T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>This brief article will walk you through how to make it easy for your users to jump immediately to common task pages within your app using Shortcuts.</p>]]></content><category term="progressive web apps" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/30-days-of-pwa/</id><title type="html"><![CDATA[✍🏻 30 days of PWA]]></title><link href="https://www.aaron-gustafson.com/notebook/30-days-of-pwa/" rel="alternate" type="text/html" /><published>2022-02-15T15:46:48Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Today, <a href="https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/kickoff?id=brought-to-you-by">some colleagues and I</a> kicked off a new series on developing Progressive Web Apps. It will run for 30 days and takes you from the point of knowing nothing about PWAs all the way through integrating some of the amazing advanced capabilities available to web apps today.</p><p>I’ll be using this post to collect the articles as they publish. You can also check out <a href="https://dev.to/nitya/series/16849">the series on dev.to</a> or on <a href="https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/">the canonical site</a>.</p><ul><li><a href="https://dev.to/azure/welcome-to-30daysofpwa-2e54">Welcome to #30DaysOfPWA</a></li><li><a href="https://dev.to/azure/01-introducing-progressive-web-apps-hi4">Day 1 - Introducing Progressive Web Apps</a></li><li><a href="https://dev.to/azure/02-deconstructing-progressive-web-apps-1884">Day 2 - Deconstructing Progressive Web Apps</a></li><li><a href="https://dev.to/azure/03-make-progressive-web-apps-installable-4g1d">Day 3 - Make Progressive Web Apps Installable</a></li><li><a href="https://dev.to/azure/04-make-progressive-web-apps-reliable-2b5o">Day 4 - Make Progressive Web Apps Reliable</a></li><li><a href="https://dev.to/azure/05-make-progressive-web-apps-work-offline-fil">Day 5 - Make Progressive Web Apps Work Offline</a></li><li><a href="https://dev.to/azure/06-make-progressive-web-apps-capable-4344">Day 6 - Make Progressive Web Apps Capable</a></li><li><a href="https://dev.to/azure/07-developing-progressive-web-apps-hfb">Day 7 - Developing Progressive Web Apps</a></li><li><a href="https://dev.to/azure/08-introducing-advanced-capabilities-of-pwas-4dj8">Day 8 - Introducing Advanced Capabilities of PWAs</a></li><li><a href="https://dev.to/azure/09-creating-application-shortcuts-m0i">Day 9 - Creating Application Shortcuts</a></li><li><a href="https://dev.to/azure/10-sharing-content-from-and-with-your-app-5hbc">Day 10 - Sharing Content From and With Your App</a></li><li><a href="https://dev.to/azure/11-displaying-content-more-like-an-app-10md">Day 11 - Displaying Content More Like An App</a></li><li><a href="https://dev.to/azure/12-caching-your-app-data-3f0j">Day 12 - Caching Your App Data</a></li><li><a href="https://dev.to/azure/13-synchronizing-app-data-in-the-background-1n6c">Day 13 - Synchronizing App Data in the Background</a></li><li><a href="https://dev.to/azure/14-notifying-users-of-updates-43gp">Day 14 - Notifying Users Of Updates</a></li><li><a href="https://dev.to/azure/15-tools-getting-started-building-new-pwa-l1o">Day 15 - Tools: Getting Started / Building New PWA</a></li><li><a href="https://dev.to/azure/16-tools-getting-started-converting-your-web-app-to-a-pwa-3p3d">Day 16 - Tools: Getting Started / Converting your web app to a PWA</a></li><li><a href="https://dev.to/azure/17-tools-debug-your-pwa-part-1-305j">Day 17 - Tools: Debug Your PWA (Part 1)</a></li><li><a href="https://dev.to/azure/18-tools-debug-your-pwa-part-2-42bp">Day 18 - Tools: Debug Your PWA (Part 2)</a></li><li><a href="https://dev.to/azure/19-tools-auditing-your-pwa-3imf">Day 19 - Tools: Auditing Your PWA</a></li><li><a href="https://dev.to/azure/20-tools-test-automation-for-pwa-21kp">Day 20 - Tools: Test Automation for PWA</a></li><li><a href="https://dev.to/azure/21-tools-packaging-your-pwa-5b7h">Day 21 - Tools: Packaging Your PWA</a></li><li><a href="https://dev.to/azure/22-best-practices-installability-for-pwa-1ka6">Day 22 - Best Practices for PWA: Installability</a></li><li><a href="https://dev.to/azure/23-best-practices-for-pwa-engagement-1i95">Day 23 - Best Practices for PWA: Engagement</a></li><li><a href="https://dev.to/azure/24-best-practices-for-pwa-distribution-35ao">Day 24 - Best Practices for PWA: Distribution</a></li><li><a href="https://dev.to/azure/25-best-practices-for-pwa-reliability-35eo">Day 25 - Best Practices for PWA: Reliability</a></li><li><a href="https://dev.to/azure/26-best-practices-for-pwa-maintainability-4nfc">Day 26 - Best Practices for PWA: Maintainability</a></li><li><a href="https://dev.to/azure/27-best-practices-for-pwa-authentication-29md">Day 27 - Best Practices for PWA: Authentication</a></li><li><a href="https://dev.to/azure/28-best-practices-for-pwa-uiux-177j">Day 28 - Best Practices for PWA: UI/UX</a></li><li><a href="https://dev.to/azure/29-recap-from-core-concepts-to-best-practices-12al">Day 29 - Recap: From Core Concepts to Best Practices!</a></li><li><a href="https://dev.to/azure/30-pwa-learning-resources-for-beginners-4l95">Day 30 - PWA: Learning Resources For Beginners</a></li></ul><p>This was a really fun series to put together; many thanks to all of my friends and colleagues who contributed. They are:</p><ul><li><a href="https://twitter.com/beth_panx">@beth_panx</a>,</li><li><a href="https://twitter.com/nitya">@nitya</a>,</li><li><a href="https://twitter.com/kennethrohde">@kennethrohde</a>,</li><li><a href="https://twitter.com/diekus">@diekus</a>,</li><li><a href="https://twitter.com/seaotta">@seaotta</a>,</li><li><a href="https://twitter.com/webmaxru">@webmaxru</a>,</li><li><a href="https://twitter.com/Justinwillis96">@Justinwillis96</a>,</li><li><a href="https://twitter.com/amruthasrin">@amruthasrin</a>,</li><li><a href="https://twitter.com/patrickbrosset">@patrickbrosset</a>,</li><li><a href="https://twitter.com/noobtiger11">@noobtiger11</a>,</li><li><a href="https://twitter.com/jaylynsatwork">@jaylynsatwork</a>,</li><li><a href="https://twitter.com/devteutsch">@devteutsch</a>,</li><li><a href="https://twitter.com/JudahGabriel">@JudahGabriel</a>, and</li><li><a href="https://twitter.com/metulev">@metulev</a>.</li></ul>]]></content><amg:summary><![CDATA[Yesterday saw the launch of a 30-day series on building Progressive Web Apps. You should follow along.]]></amg:summary><summary type="html"><![CDATA[<p>Yesterday saw the launch of a 30-day series on building Progressive Web Apps. You should follow along.</p>]]></summary><category term="progressive web apps" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2022-02-15/hero.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/announcing-pwa-studio-the-vs-code-extension-for-building-progressive-web-apps-/</id><title type="html"><![CDATA[🔗 Announcing PWA Studio, the VS Code extension for building Progressive Web Apps!]]></title><link href="https://www.aaron-gustafson.com/notebook/links/announcing-pwa-studio-the-vs-code-extension-for-building-progressive-web-apps-/" rel="alternate" type="text/html" /><link href="https://blog.pwabuilder.com/posts/announcing-pwa-studio-the-vs-code-extension-for-building-progressive-web-apps!/" rel="related" type="text/html" /><published>2022-02-04T17:16:59Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Building (and improving) PWAs in VSCode just got even better. Many thanks to the PWA Builder team for all their hard work!</p>]]></content><amg:twitter><![CDATA[Building (and improving) PWAs in @code just got even better. Many thanks to the @PWABuilder team for all their hard work!]]></amg:twitter><category term="progressive web apps" /><category term="web development" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.pwabuilder.com/posts/announcing-vscode/vscode-release-graphic.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/breaking-out-of-the-box/</id><title type="html"><![CDATA[🔗 Breaking Out of the Box]]></title><link href="https://www.aaron-gustafson.com/notebook/links/breaking-out-of-the-box/" rel="alternate" type="text/html" /><link href="https://alistapart.com/article/breaking-out-of-the-box/" rel="related" type="text/html" /><published>2021-12-09T16:29:28Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Years ago we started thinking about how PWAs could extend their reach into the titlebar (like Electron apps can) and it’s finally a reality! Check out this deep dive from Patrick Brosset to learn how to do it with just a little bit of CSS and/or JavaScript.</p>]]></content><amg:twitter><![CDATA[Make your PWA look more like an app by building your own titlebar. @patrickbrosset shows you how.]]></amg:twitter><category term="progressive web apps" /><category term="JavaScript" /><category term="CSS" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://i0.wp.com/alistapart.com/wp-content/uploads/2021/12/WindowControlsOverlay.png?fit=1200%2C825&amp;ssl=1" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/what-s-new-with-pwas-/</id><title type="html"><![CDATA[🔗 What's new with PWAs?]]></title><link href="https://www.aaron-gustafson.com/notebook/links/what-s-new-with-pwas-/" rel="alternate" type="text/html" /><link href="https://conye.netlify.app/whats-new-with-PWAs/" rel="related" type="text/html" /><published>2021-11-30T23:58:18Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Chidiebere Onyegbuchulem has assembled a huge list of awesome features that could work well for your Progressive Web App(s).</p>]]></content><amg:twitter><![CDATA[A great overview of the latest #PWA features from @chidexebere.]]></amg:twitter><category term="progressive web apps" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="404" /></entry><entry><id>https://www.aaron-gustafson.com/appearances/podcasts/2021-11-02-progressive-web-apps-with-aaron-gustafson/</id><title type="html"><![CDATA[🎧 Progressive web apps with Aaron Gustafson]]></title><link href="https://podrocket.logrocket.com/pwas" rel="alternate" type="text/html" /><published>2021-11-02T00:00:00Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I joined Ben and Kaelan to talk about all things PWA.</p>]]></content><category term="progressive web apps" /><category term="web development" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/going-dark-mode/</id><title type="html"><![CDATA[✍🏻 Going dark (mode)]]></title><link href="https://www.aaron-gustafson.com/notebook/going-dark-mode/" rel="alternate" type="text/html" /><published>2021-10-26T18:57:19Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>While working on tooling to analyze Web App Manifest usage in relation to some new feature proposals, it became clear we needed a test Manifest that included the proposed syntax for dark/light mode support. I decided to make this site the guinea pig and spent an hour or so tweaking things to make it happen. Here’s a run-down of what I did:</p><h2 id="css-tweaks" tabindex="-1"><a class="header-anchor" href="#css-tweaks" aria-hidden="true">#</a> CSS tweaks</h2><p>The first step was to add in the <code>prefers-color-scheme</code> Media Query. If you’re unfamiliar, it looks a little something like this:</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token comment">/* Your regular rules go here <em>/</span><span class="token atrule"><span class="token rule">@media</span><span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token comment">/</em> Overrides for the “dark” theme go here <em>/</span><span class="token punctuation">}</span><span class="token atrule"><span class="token rule">@media</span><span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> light<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token comment">/</em> Overrides for the “light” theme go here */</span><span class="token punctuation">}</span></code></pre><p>I only wanted to add a “dark” theme as my default is pretty much a “light” theme anyway. For the most part, this was pretty straightforward… just swapping color values, being sure to use the specific properties I wanted to change (e.g., <code>background-color</code>, <code>border-color</code>) rather than the shorthand. The only tricky/convoluted bit was updating my fancy link underlines (which don’t use <code>text-decoration</code>).</p><h2 id="tweaking-svgs" tabindex="-1"><a class="header-anchor" href="#tweaking-svgs" aria-hidden="true">#</a> Tweaking SVGs</h2><p>You may not have realized it, but SVGs also support the <code>prefers-color-scheme</code> Media Query. Most of my SVGs were black &amp; white already, so I had no color definitions in them. Adding an embedded stylesheet to swap out colors did the trick though:</p><pre class="language-css" tabindex="0"><code class="language-css"><span class="token selector">svg</span><span class="token punctuation">{</span><span class="token property">background-color</span><span class="token punctuation">:</span> white<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">path</span><span class="token punctuation">{</span><span class="token property">fill</span><span class="token punctuation">:</span> black<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token atrule"><span class="token rule">@media</span><span class="token punctuation">(</span><span class="token property">prefers-color-scheme</span><span class="token punctuation">:</span> dark<span class="token punctuation">)</span></span><span class="token punctuation">{</span><span class="token selector">svg</span><span class="token punctuation">{</span><span class="token property">background-color</span><span class="token punctuation">:</span> #454545<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token selector">path</span><span class="token punctuation">{</span><span class="token property">fill</span><span class="token punctuation">:</span> #fffcf4<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>While most of these SVGs are in my HTML, some are referenced in my Web App Manifest. At the time I’m writing this, no browsers support SVG icons in the Manifest, but it’s something we (the Edge team) are working on adding for Chromium. And, when we land it, my hope is that we’ll include dark/light icon support by rastorizing the vector files in each of these contexts.</p><h2 id="tweaking-the-manifest" tabindex="-1"><a class="header-anchor" href="#tweaking-the-manifest" aria-hidden="true">#</a> Tweaking the Manifest</h2><p>Continuing in the realm of speculation, there’s <a href="https://github.com/w3c/manifest/issues/975#issuecomment-960222756">a proposal to support user-prefered color schemes in the Manifest</a> as well. That proposal calls for a <code>user_preferences</code> block, wherein certain keys can be redefined. Here’s what I did, based on our discussions:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“user_preferences”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“color_scheme_dark”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“background_color”</span><span class="token operator">:</span><span class="token string">“#5b5b5b”</span><span class="token punctuation">,</span><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icon.svg”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/svg+xml”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“purpose”</span><span class="token operator">:</span><span class="token string">“any monochrome maskable”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icon-reverse.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“purpose”</span><span class="token operator">:</span><span class="token string">“any monochrome maskable”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/notification-icon-reverse.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“256x256”</span><span class="token punctuation">,</span><span class="token property">“purpose”</span><span class="token operator">:</span><span class="token string">“any monochrome maskable”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">,</span></code></pre><p>I’m not sure yet whether my color-adapting SVG icon would need to be included here so as not to be replaced by the PNG versions, so I went ahead and included it anyway.</p><hr><p>And that’s pretty much it. With just a little bit of time, I got it all set up. If you happen to use the new theme and something looks wonky, please let me know.</p>]]></content><amg:summary><![CDATA[I finally got around to playing with the CSS user color-scheme preference and enabled a “dark mode” for this site.]]></amg:summary><summary type="html"><![CDATA[<p>I finally got around to playing with the CSS user color-scheme preference and enabled a “dark mode” for this site.</p>]]></summary><category term="CSS" /><category term="progressive web apps" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2021-11-19/hero.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/enhancing-the-manifest/</id><title type="html"><![CDATA[✍🏻 Enhancing the Manifest]]></title><link href="https://www.aaron-gustafson.com/notebook/enhancing-the-manifest/" rel="alternate" type="text/html" /><published>2021-10-26T18:57:19Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Since joining the esteemed group of editors maintaining the <a href="https://www.w3.org/TR/appmanifest/">Web App Manifest spec</a> for the <a href="https://www.w3.org/">W3C</a>, I’ve been on the lookout for ways to enhance both web apps themselves—in terms of functionality—and how web apps are represented in app catalogs and digital storefronts. Some of that work is finally gaining traction and I’d love to get your input.</p><p>I briefly presented on a bunch of the different efforts I’m involved with at the <a href="https://www.w3.org/2021/10/TPAC/">W3C’s TPAC</a> yesterday. Here’s a rundown of what I discussed as well as links you can follow if you want to get involved.</p><h2 id="indicating-an-app%E2%80%99s-policies-%26-other-legalese" tabindex="-1"><a class="header-anchor" href="#indicating-an-app%E2%80%99s-policies-%26-other-legalese" aria-hidden="true">#</a> Indicating an app’s policies &amp; other legalese</h2><p>Many app catalogs enable developers to provide links to things like their app’s Privacy Policy and Terms of Use. There is no way, however, to semantically indicate this information in your markup; the closest you can get is <code>link[rel=&quot;license&quot;]</code>, but that’s limited in scope. Rather than propose including more <code>link</code> or <code>meta</code> elements (which would need to be included on <em>every</em> page of your site), we think it makes more sense to roll this information into the Manifest as part of a <code>policies</code> object.</p><p><a href="https://github.com/w3c/manifest-app-info/pull/46">My proposal</a> enables developers to provide URLs for an app’s accessibility statement, content license, and policies governing privacy, security, support, and usage. Developers would be free to include whichever policies their app has and they can be URLs within the app itself or link off to other websites (such as the publisher’s). Here’s what that could look like:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“policies”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“accessibility”</span><span class="token operator">:</span><span class="token string">“/accessibility”</span><span class="token punctuation">,</span><span class="token property">“usage”</span><span class="token operator">:</span><span class="token string">“/terms”</span><span class="token punctuation">,</span><span class="token property">“privacy”</span><span class="token operator">:</span><span class="token string">“<a href="http://publisher.tld/privacy">http://publisher.tld/privacy</a>”</span><span class="token punctuation">}</span></code></pre><p>So far, this seems to meet the needs of several of the app catalogs I’ve spoken with. I don’t know that browsers will make use of this information, but I could see it being useful in the context of an installed PWA as well as in app listings within the browser UI too.</p><p>In a related effort, I’m pushing for the Microsoft Store to support accessibility statements, which are helpful for explaining application functionality and providing details of any known accessibility limitations within an app.</p><h2 id="identifying-an-app%E2%80%99s-publisher" tabindex="-1"><a class="header-anchor" href="#identifying-an-app%E2%80%99s-publisher" aria-hidden="true">#</a> Identifying an app’s publisher</h2><p>When you’re considering installing any app, you want to know you can trust who’s behind it. Phishing sites are everywhere though, so we have to believe that phishing apps are here too (or not too far off). You want to be able to trust that an app purporting to be from your bank actually is from your bank. With such high stakes, opening up the possibility for an app to identify its publisher is quite dangerous, which is why this proposal has taken a while to germinate.</p><p>From the developer side of things, <a href="https://github.com/w3c/manifest-app-info/pull/47">my proposed implementation</a> is relatively straightforward. First, the app identifies its <code>publisher</code> in the Manifest:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“publisher”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Organization Name”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://organization.tld/">https://organization.tld/</a>”</span><span class="token punctuation">}</span></code></pre><p>The problem is that I could put anything in there, declaring my publisher to be any company or individual. In order to substantiate my app’s claim, the listed publisher needs to claim the app too. The publisher could do this by enumerating the apps it owns in a text file located at <code><a href="https://organization.tld/.well-known/published-web-apps">https://organization.tld/.well-known/published-web-apps</a></code>:</p><pre class="language-txt" tabindex="0"><code class="language-txt"><a href="https://origin1.tld/manifest.json">https://origin1.tld/manifest.json</a><a href="https://origin2.tld/static/manifest.json">https://origin2.tld/static/manifest.json</a></code></pre><p>Instead of using a text file, it may be possible to piggyback on the proposed <a href="https://web.dev/pwa-url-handler/#the-web-app-origin-association-file"><code>web-app-origin-association</code> file</a>, but I’m concerned with <a href="https://github.com/w3c/manifest-app-info/pull/47#issuecomment-952169632">overloading <code>web-app-origin-association</code> and confusing developers</a> who are using it for URL handling.</p><p>Regardless of the mechanism, even with this bi-directional attestation, however, the system still has a gaping security hole: the <code>publisher[&quot;name&quot;]</code> string can’t be validated this way. It would be relatively easy for an attacker to host 2 websites, one of which looks like your bank’s app and the other which merely hosts the <code>published-web-apps</code> file in order to support the bogus app’s claim of being published by your bank.</p><p>This is where <a href="https://github.com/w3c/manifest-app-info/pull/47/files#diff-0eb547304658805aad788d320f10bf1f292797b5e6d745a3bf617584da017051R504-R512">the proposed processing algorithm adds an opaque validation step</a>. Implementors are encouraged to verify that the publisher’s domain actually matches the claimed name. There are a number of services that offer name-to-url (or <em>vice versa</em>) lookup, but an implementor could even have a manual process in place to verify the provided information (which app catalogs do as a regular part of their business).</p><p>In the end, the publisher value will either be a validated name and URL pair or, if validation fails, the web app’s URL host string (e.g., “<a href="http://twitter.com">twitter.com</a>”), which many implementors already use as the publisher name.</p><h2 id="enabling-server-side-content-negotiation" tabindex="-1"><a class="header-anchor" href="#enabling-server-side-content-negotiation" aria-hidden="true">#</a> Enabling server-side content negotiation</h2><p>Though many folks these days focus their efforts on running as much code as possible on the client, there is a lot of value in making significant changes to your app on the server, before responding to a request. We’ve heard a number of use cases from folks building PWAs and have put together three proposals to help address them:</p><ol><li><strong><a href="https://github.com/w3c/manifest-app-info/pull/32">A Client Hint to indicate whether the app is installed</a>.</strong> The <code>Sec-CH-App-Installed</code> header would give a server information about whether or not a user agent is representing the app in an “installed” state. The value would be a boolean <code>true</code> or <code>false</code>. We are discussing <a href="https://github.com/w3c/manifest-app-info/pull/32#issuecomment-825288114">exposing this information via a DOM API as well</a>.</li><li><strong><a href="https://github.com/w3c/manifest/pull/977">A Client Hint to indicate the <code>display</code> mode</a>.</strong> We already have a way of detecting the <code>display</code> mode of a web app using <code>matchMedia()</code> and <a href="https://w3c.github.io/manifest/#the-display-mode-media-feature">CSS’s <code>display-mode</code> media feature</a>, but that is all client side code. The <code>Sec-CH-Display-Mode</code> header would provide the <code>display</code> value (taking into account <a href="https://github.com/w3c/manifest/pull/932"><code>display_override</code></a>) on the server side as well.</li><li><strong><a href="https://github.com/w3c/manifest-app-info/issues/19#issuecomment-858137411">Using the <code>Referer</code> header to indicate installation source</a></strong>. I was working with a partner that wanted to beta their PWA to only people who’d installed their PWA from a specific app catalog. Even if they had supplied a separate Manifest with a unique <code>start_url</code> to that catalog, that would not have allowed them to limit their app’s distribution if the URL was shared. And so we proposed that the browser provide a catalog-associated <code>Referer</code> header when the PWA is first launched (and in the absence of a true referral). This feature has shipped in Edge for PWAs installed via the Microsoft Store and we are encouraging other app catalogs and browsers to consider the same approach.</li></ol><p>It’s worth noting that all of these features can be used for analytics and (potentially) to violate someone’s privacy. That is why we’ve suggested they sit under the umbrella of the <a href="https://developer.chrome.com/docs/privacy-sandbox/overview/">Privacy Sandbox</a>.</p><h2 id="adapting-app-ui-to-user-preferences" tabindex="-1"><a class="header-anchor" href="#adapting-app-ui-to-user-preferences" aria-hidden="true">#</a> Adapting app UI to user preferences</h2><p>Shortly after OSes began rolling out support for color schemes (e.g., “light” and “dark” modes), I saw a need for web apps to be able to adapt to these user preferences. In CSS we have the <a href="https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-scheme"><code>prefers-color-scheme</code> Media Query</a>, but the Manifest had no way of accounting for these sorts of changes. My initial proposal centered around <a href="https://github.com/w3c/image-resource/issues/26">adding support for Media Queries in the <code>ImageResource</code> spec</a>. Short of having CSS-driven, adaptive SVG web app icons (which I’m also working on), being able to associate an <code>ImageResource</code> bitmap with a Media Query seems like the way to go.</p><p>Here’s what that might look like in the context of the <code>icons</code> member:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/icons/play-later.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“size”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“media”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: light)”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/icons/play-later-reverse.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“size”</span><span class="token operator">:</span><span class="token string">“512x512”</span><span class="token punctuation">,</span><span class="token property">“media”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: dark)”</span><span class="token punctuation">}</span><span class="token punctuation">]</span></code></pre><p>This approach would be applicable wherever <code>ImageResource</code> gets used, which includes app icons, shortcut icons, and screenshots within the context of the Manifest as well as the Notifications and Payment Request APIs.</p><p>Beyond images, however, <a href="https://github.com/w3c/manifest/issues/975">as Jen Simmons pointed out, we need the ability to adapt colors within the Manifest as well</a>. It’s early days in discussing this right now, but we’re thinking about adding a generic means of redefining Manifest keys, based on a particular context. It is going to be a challenge to figure this out because we don’t want to force Manifest authors to redefine an entire nested structure (such as a shortcut) just to swap out a single part of that object. But more on that in a moment.</p><p>Here is a simple example of how this might look:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“user_preferences”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“context”</span><span class="token operator">:</span><span class="token string">“(prefers-color-scheme: dark)”</span><span class="token punctuation">,</span><span class="token property">“redefine”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“theme_color”</span><span class="token operator">:</span><span class="token string">“#bdbdbd”</span><span class="token punctuation">,</span><span class="token property">“background_color”</span><span class="token operator">:</span><span class="token string">“#000000”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span></code></pre><p>Ideally developers would not use this mechanism for redefining their icons, using the <code>ImageResource</code> option instead, but it does open the door to complexity.</p><h2 id="web-app-translations" tabindex="-1"><a class="header-anchor" href="#web-app-translations" aria-hidden="true">#</a> Web app translations</h2><p>Speaking of complexity, have you ever tried to build a web app that is translated into multiple languages? It’s an incredibly challenging task and the Manifest does not make it any easier. At present, the guidance for creating multi-lingual Manifests is to use a separate file for each language or make it a dynamic file that can be adjusted on the server side based on path or query string adjustments.</p><p>I’m currently working on <a href="https://github.com/w3c/manifest/issues/676">a proposal to address this within a single document by introducing a <code>translations</code> member</a>. My proposal offers two approaches, one favoring simpler Manifests, the other more useful for complex Manifests.</p><p>For simple Manifests, translations could be embedded directly within the Manifest file as members of the translations object, keyed by language code:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>For complex Manifests that may need to redefine text in shortcuts, screenshot labels, and the like, I proposed enabling translations to be managed in a separate file, offering alternative strings only for the items that need it. Using this approach, the above example could become:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token comment">// manifest.json</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token string">“manifest.fr.json”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token comment">// manifest.fr.json:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">}</span></code></pre><p>One serious challenge (as with the <code>user_preferences</code> block, discussed above) is how to manage changing individual parts of a complex Manifest component. For example, you would likely need to change the text label of a shortcut, but may not need to change its <code>url</code> or icon. <a href="https://github.com/w3c/manifest/issues/975#issuecomment-870430956">Thomas Steiner suggested</a> that <a href="https://github.com/json-path/JsonPath#getting-started">JSON Path</a> may be a good solution here as it could enable you to do something like</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Good dog”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“An app for dogs”</span><span class="token punctuation">,</span><span class="token property">“lang”</span><span class="token operator">:</span><span class="token string">“en”</span><span class="token punctuation">,</span><span class="token property">“shortcuts”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Pet Me”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“/pet-me/”</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Feed Me”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“/feed-me/”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“translations”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“fr”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Bon chien”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“Une application pour chiens”</span><span class="token punctuation">,</span><span class="token property">“$[‘shortcuts’][0][‘name’]”</span><span class="token operator">:</span><span class="token string">“Nourrissez-moi”</span><span class="token punctuation">,</span><span class="token property">“$[‘shortcuts’][1][‘name’]”</span><span class="token operator">:</span><span class="token string">“Caressez-moi”</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>This is still something we are actively working through, but it’s promising for sure.</p><h2 id="consider-getting-involved" tabindex="-1"><a class="header-anchor" href="#consider-getting-involved" aria-hidden="true">#</a> Consider getting involved</h2><p>There is a lot of really interesting work happening in Manifest-land these days. If you’d like to get involved, please follow the links and chime in on the discussions. We’d love to have you!</p>]]></content><amg:summary><![CDATA[I briefly presented on a bunch of the different efforts I’m involved with at the W3C’s TPAC yesterday. Here’s a rundown of what I discussed as well as links you can follow if you want to get involved.]]></amg:summary><summary type="html"><![CDATA[<p>I briefly presented on a bunch of the different efforts I’m involved with at the W3C’s TPAC yesterday. Here’s a rundown of what I discussed as well as links you can follow if you want to get involved.</p>]]></summary><category term="progressive web apps" /><category term="web standards" /></entry></feed>