<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title>Blogccasion</title>
	<subtitle>A look inside the think tank</subtitle>
	<link href="https://blog.tomayac.com/feed/feed.xml" rel="self"/>
	<link href="https://blog.tomayac.com"/>
	<link href="https://ilp.gatehub.net/348218105/eur" rel="monetization"/>
	<updated>2026-10-17T17:21:12Z</updated>
	<id>https://blog.tomayac.com/</id>
	<author>
		<name>Thomas Steiner</name>
		<email>steiner.thomas@gmail.com</email>
	</author>
	
	
	
	
	<entry>
		<title>Getting my TOTP data out of Twilio&#39;s Authy app thanks to the GDPR</title>
		<link href="https://blog.tomayac.com/2026/10/17/getting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr/"/>
		<updated>2026-10-17T17:21:12Z</updated>
		<id>https://blog.tomayac.com/2026/10/17/getting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr/</id>
		<content type="html">
			&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/gdpr-europe.jpg&quot; alt=&quot;The GDPR symbol with a torn Authy logo in the lower right corner.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;As every responsible Internet user, I use &lt;a href=&quot;https://en.wikipedia.org/wiki/Multi-factor_authentication&quot;&gt;two-factor authentication&lt;/a&gt; (2FA) for my important accounts. For a long time, I depended on Google&#39;s &lt;a href=&quot;https://support.google.com/accounts/answer/1066447?hl=en&amp;amp;co=GENIE.Platform%3DAndroid&quot;&gt;Authenticator&lt;/a&gt; app, but because early versions of the app weren&#39;t backed up to the cloud (not a problem anymore) and because as a Google Android dogfooder I switch devices frequently, I turned to Twilio&#39;s &lt;a href=&quot;https://www.authy.com/&quot;&gt;Authy&lt;/a&gt;, which at the time already supported cloud backup and cross device synchronization. Authy has both iOS and Android apps, and also offered a desktop app, but for whatever reason they shut down the desktop app three years ago.&lt;/p&gt;
&lt;p&gt;Now every time I needed a token, I had to get one of my phones out. Annoying, but not annoying enough for me to actually switch away from Authy. I have 33 accounts in there, and unfortunately Authy offers no export option. For a while, you could hack your way to your data by installing an old version of their desktop app and sniffing the network traffic, but they eventually patched that loophole. Modern Android and iOS versions (for good reason) also make it incredibly hard to sniff mobile traffic, plus I have corporate devices that prevent me from installing the required apps and certificates.&lt;/p&gt;
&lt;p&gt;So my data was locked into Authy, with my choices being to just be lazy and stick with it, or bite the bullet and set up 2FA again for each account. Guess what I did! Correct, I entered each account one by one, turned off 2FA, set it up agai… Haha, LOL, of course I did not. I stuck with Authy and walked to one of my phones like a Neanderthal every time I needed a token.&lt;/p&gt;
&lt;p&gt;Until I remembered that under the European Union&#39;s 🇪🇺 &lt;a href=&quot;https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng&quot;&gt;General Data Protection Regulation&lt;/a&gt; (GDPR), I have the right to request my personal data from any service provider, including Authy. If you live in the EU, you have, too. Here&#39;s the request I sent to Twilio (&lt;a href=&quot;mailto:privacy@twilio.com&quot;&gt;privacy@twilio.com&lt;/a&gt;) to get my TOTP data out of Authy:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dear Twilio Privacy Team,&lt;/p&gt;
&lt;p&gt;I am a resident of [Your EU country] and a user of the Authy application. Under the EU General Data Protection Regulation, I am exercising my right of access (Article 15) and my right to data portability (Article 20) with respect to the personal data you process about me.&lt;/p&gt;
&lt;p&gt;Please provide a complete copy of all personal data associated with my Authy account, identified by:&lt;/p&gt;
&lt;p&gt;Authy ID: [Your Authy ID]&lt;br /&gt;
Phone number: [Your phone number]&lt;br /&gt;
Contact email: [Your contact email]&lt;/p&gt;
&lt;p&gt;I specifically request that your response include the stored authenticator data for my account, namely the encrypted TOTP token seed values together with any associated salts, initialization parameters, and key-derivation parameters required to decrypt them, and, for each token, the issuer or account name, the number of digits, the period, and the algorithm. Please also specify the encryption scheme used to protect the token seeds, including the cipher and mode, the key-derivation function, its iteration count, and the hash function. The token seeds are stored encrypted under a password known only to me, and I am asking for that encrypted data as you hold it, so that I can decrypt it myself.&lt;/p&gt;
&lt;p&gt;Under Article 20, please provide the token data in a structured, commonly used, and machine-readable format, for example JSON.&lt;/p&gt;
&lt;p&gt;Please confirm receipt of this request and respond within one month, as required by Article 12(3). If you consider any part of this request unfounded or excessive, please explain your reasoning for each element rather than declining as a whole.&lt;/p&gt;
&lt;p&gt;Thank you.&lt;/p&gt;
&lt;p&gt;Kind regards,&lt;br /&gt;
[Your name]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They came back quickly, asking for the following verification details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All current and past email addresses associated with the Authy account.&lt;/li&gt;
&lt;li&gt;All current and past phone numbers associated with the Authy account.&lt;/li&gt;
&lt;li&gt;A confirmation code sent through text message to the number linked to the account, which should be sent back with the reply.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I responded with the requested information, and a few weeks later they sent me a link to a secure file transfer on &lt;a href=&quot;https://twilio.sendsafely.com/&quot;&gt;twilio.sendsafely.com&lt;/a&gt; and let me download two CSV files with the data. One was an &lt;code&gt;Authy Personal Information Request - Exported Tokens (1).csv&lt;/code&gt; file with the columns &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;encrypted_seed&lt;/code&gt;, &lt;code&gt;salt&lt;/code&gt;, and &lt;code&gt;iv&lt;/code&gt;. The &lt;code&gt;name&lt;/code&gt; is just the name of the token I had chosen. The others are explained below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;encrypted_seed&lt;/code&gt;: The actual TOTP secret (the seed used to generate 2FA codes), encrypted so it&#39;s unreadable in plain text.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;salt&lt;/code&gt;: Random data mixed in when deriving the encryption key from the encryption password (prevents precomputed &amp;quot;rainbow table&amp;quot; attacks and ensures the same password doesn&#39;t always produce the same key).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iv&lt;/code&gt;: The random value (initialization vector) fed into the cipher itself, ensuring that encrypting the same seed twice (even with the same key) produces different ciphertext each time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other file, &lt;code&gt;Authy Personal Information Request - Authy Personal Information (2).csv&lt;/code&gt;, was a text file (despite the &lt;code&gt;.csv&lt;/code&gt; extension) with CSV-ish metadata about my account and its associated tokens.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;,,,,,
User Information,,,,,
Phone number(s),&amp;quot;(+49) [REDACTED]
&amp;quot;,,,,
E-mail (primary and secondary),&amp;quot;[REDACTED], [REDACTED]&amp;quot;,,,,
&amp;quot;Last Sync (The last time one of your devices communicated to Authy)
-Time is in UTC&amp;quot;,&amp;quot;Oct 10, 2026 - 06:37
&amp;quot;,,,,
&amp;quot;Used at (The last time you verified an Authy TOTP token)
-Time is in UTC&amp;quot;,&amp;quot;Jun 01, 2018 - 10:27
&amp;quot;,,,,
&amp;quot;Created (When the Authy user was created)
-Time is in UTC&amp;quot;,&amp;quot;Jul 06, 2017&amp;quot;,,,,
,,,,,
Devices linked to the Authy user,,,,,
Name,Created,Device Creation IP,User agent,Device ID,Last sync
Android,&amp;quot;Sep 30, 2019&amp;quot;,[REDACTED],Dalvik/2.1.0 (Linux; U; Android 10; Pixel 3a Build/QQ1A.190919.002) Mobile,[REDACTED],&amp;quot;Sep 18, 2021&amp;quot;
[SNIP]
Android,&amp;quot;Jun 23, 2026&amp;quot;,[REDACTED],Dalvik/2.1.0 (Linux; U; Android 17; Pixel 11 Pro Fold Build/CD1A.260612.002) Mobile,[REDACTED],&amp;quot;Oct 10, 2026&amp;quot;
,,,,,
Authy native tokens,,,,,
ID,Name,Email,Cellphone,Member/Owner,
[REDACTED],Twilio,[REDACTED],[REDACTED],Member,
,,,,,
Authenticator tokens,,,,,
Name,Logo,Created at,Updated at,,
GitHub:tomayac,GitHub,&amp;quot;Jul 17, 2025&amp;quot;,&amp;quot;Jul 17, 2025&amp;quot;,,
[SNIP]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I threw both files at Claude and asked it to create me a &lt;a href=&quot;https://github.com/tomayac/totp-vault&quot;&gt;TOTP Vault PWA&lt;/a&gt; that going forward I can use on all of my devices, desktop or mobile, with the TOTP data backed up to a private GitHub repo. After a few iterations back and forth with the LLM, I can now scan QR codes and add new TOTP tokens seamlessly using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API&quot;&gt;Barcode Detector API&lt;/a&gt;. The app is fully offline capable thanks to &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API&quot;&gt;Service Workers&lt;/a&gt;. It looks almost like a native app because I use &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window_Controls_Overlay_API&quot;&gt;Window Controls Overlay&lt;/a&gt;. Each TOTP line has a guessed (but overrideable) icon provided by &lt;a href=&quot;https://icon.horse/&quot;&gt;Icon Horse&lt;/a&gt;. And it is &lt;em&gt;my&lt;/em&gt; PWA with features tailored to my needs, like always sorting entries by most recently used, a search input that clears after copying the token, plus, of course, token import and export functionality for all possible providers and standards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CXF:&lt;/strong&gt; The FIDO &lt;a href=&quot;https://fidoalliance.org/specs/cx/cxf-v1.0-wd-20240522.html&quot;&gt;Credential Exchange Format&lt;/a&gt;, unencrypted, for password managers that import it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bitwarden / Vaultwarden:&lt;/strong&gt; Unencrypted JSON for importing data in &lt;a href=&quot;https://bitwarden.com/&quot;&gt;Bitwarden&lt;/a&gt; / &lt;a href=&quot;https://github.com/dani-garcia/vaultwarden&quot;&gt;Vaultwarden&lt;/a&gt;. I use Vaultwarden as a backup on my devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;otpauth links:&lt;/strong&gt; An unencrypted text file with one &lt;a href=&quot;https://www.ietf.org/archive/id/draft-andesco-otpauth-uri-00.html&quot;&gt;&lt;code&gt;otpauth://&lt;/code&gt;&lt;/a&gt; link per line, for apps like &lt;a href=&quot;https://ente.com/auth/&quot;&gt;Ente Auth&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encrypted vault&lt;/strong&gt; (not really a standard, but what I use for my backups): A CXF document inside a &lt;a href=&quot;https://www.rfc-editor.org/info/rfc7516/&quot;&gt;JSON Web Encryption&lt;/a&gt; envelope, locked with my password. Yes, the below screenshot is redacted to not show any sensitive TOTP data like the actual account names or the token values (though the latter is really just me being a &lt;a href=&quot;https://en.wikipedia.org/wiki/Tin_foil_hat&quot;&gt;tin foil hat&lt;/a&gt;, it&#39;s totally unnecessary).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/totp-vault-pwa.png&quot; alt=&quot;TOTP Vault PWA screenshot with several apps and their generated TOTPs.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s kind of sad that I had to use the &lt;a href=&quot;https://eur-lex.europa.eu/eli/reg/2016/679/oj/eng&quot;&gt;GDPR&lt;/a&gt; sledgehammer to get my own TOTP data out of Twilio&#39;s Authy, but at least now I have full control over it, and I&#39;m, once more, thankful that the GDPR exists. I would also like to credit Authy for dealing very professionally and promptly with my data request.&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2026%2F10%2F17%2Fgetting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr%2F&amp;dp=%2F2026%2F10%2F17%2Fgetting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr%2F&amp;dt=Getting%20my%20TOTP%20data%20out%20of%20Twilio&#39;s%20Authy%20app%20thanks%20to%20the%20GDPR&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2026/10/17/getting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr/&quot;&gt;https://blog.tomayac.com/2026/10/17/getting-my-totp-data-out-of-twilios-authy-app-thanks-to-the-gdpr/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Refueling an EV be like</title>
		<link href="https://blog.tomayac.com/2026/10/08/refueling-an-ev-be-like/"/>
		<updated>2026-10-08T18:42:32Z</updated>
		<id>https://blog.tomayac.com/2026/10/08/refueling-an-ev-be-like/</id>
		<content type="html">
			&lt;p&gt;This is a post for people who never drove an electric vehicle (EV) before.&lt;/p&gt;
&lt;p&gt;Imagine for a moment that for refueling your car you had to open an app to
search for gas stations and filter them by whether they have Diesel, and then
whether the pump delivers a decent stream of Diesel, or just by the drop. Next,
when you found one, imagine you had to go to the pump&#39;s reviews in the app to
estimate the odds that judging from the most recent comments there actually is
Diesel.&lt;/p&gt;
&lt;p&gt;When you arrive at the designated point, imagine that the gas pump is somewhere
randomly hidden in a sparely lit industrial area parking lot. To start fueling,
imagine you needed that random gas station brand&#39;s own RFID card that of course
you don&#39;t have, or an app. Fine, imagine you scanned the QR code on the pump to
download the app, only to find that the app isn&#39;t available in the country your
phone&#39;s app store is registered in. Dead end. Cool, cool, there&#39;s another gas
station within 15km, just still within the remaining range.&lt;/p&gt;
&lt;p&gt;When you arrive at the other gas station, luckily their app is downloadable
because they published their app globally. 75MB on a crappy 3G network. You
finally have the app. Now you need to create an account. Email, phone number,
national ID, address; whatever, fine. At this point, you&#39;d sell a kidney for the
right to fuel your car. Finally the SMS account confirmation arrives.
Notification permission? Sure. Location access? Fine. Get access to your photos?
Right. Wait, what? Ah, they need you to scan a QR code on the pump to start
fueling, so that must be why they&#39;re asking. Likely…?&lt;/p&gt;
&lt;p&gt;Oh, in order to proceed, verify your email. Alright, verified and logged in. But
on their website in the app. Weird, you thought you needed to download an app
because only the app can get you fuel, and now apparently the website can? Hmm,
now there&#39;s a link to log in to the app, on the website. Email. Password? Ah,
it&#39;s in the browser&#39;s password manager. Wait, twice actually , once with your
national ID as the user name and once with your email. Likely you signed up in
the past on a long gone phone.&lt;/p&gt;
&lt;p&gt;Well… The new credentials luckily work. Pasted from the browser&#39;s password
manager into the app because of course they built it with whatever framework the
Android password manager doesn&#39;t support. Finally logged in. Choose how much
Diesel you want and from what pump. To do so, scan the QR code on the pump or
enter the pump ID manually. Luckily the ID is still readable while the QR code
is covered by random local soccer club ultras fan stickers someone placed there.
You&#39;re that close to fueling.&lt;/p&gt;
&lt;p&gt;Pre-authorize the fuel purchase by accepting the charge in your bank&#39;s app.
Pre-payment accepted, please go back to the app. Oh, sorry, unfortunately the
fueling capability is currently not available. Please try again later. Hmm,
maybe you were not supposed to plug in the nozzle before you go through the
payment dance? Try again, hoping the gas station company charges you back for
the &amp;quot;finished fueling&amp;quot; that never started. OK, finally the Diesel is flowing,
time for a well-deserved coffee. Ah, wait, there&#39;s nothing open because you&#39;re
in the middle of nowhere on an industrial area parking lot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt=&quot;Tom in jeans and a black hoodie with the paperwork in his hands standing next to a brand-new white 2021 Hyundai Kona Electric at the car dealer.&quot; src=&quot;https://blog.tomayac.com/images/kona.jpg&quot; width=&quot;2366&quot; height=&quot;1774&quot; /&gt;
&lt;figcaption&gt;Me and our 2021 Hyundai Kona Electric at the car dealer.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The above is an extreme case of an EV charging experience, narrated for fossil
fuel drivers. But it actually happened. Here&#39;s what I would like to see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every EV charging station has to accept the exact same cashless payment
methods they allow for fossil fueling. In Europe, this would essentially be
credit and debit cards. Support app payment if you want, but don&#39;t make it the
only option.&lt;/li&gt;
&lt;li&gt;Municipalities should stop building slow charging infrastructure, unless maybe
in residential areas. Instead, we need fast DC charging stations widely
available.&lt;/li&gt;
&lt;li&gt;Every gas station with fossil fuel pumps should have to have fast EV charging
stations as well, in relationship to the number of fossil fuel pumps they
have, with regulation enforcing a transition to electric mobility over time.
Today it might be one fast charger for every four fossil fuel pumps, gradually
increasing over the years.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh, I forgot to say that the sun has been refueling our two EVs, the Hyundai
Kona from the picture above and a Peugeot e-208, for free for the last several
years ☀️… You should really get an EV, and when you can, get solar as well.
Don&#39;t let my story discourage you, but it&#39;s really something I want to see
change, and more awareness raised for!&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2026%2F10%2F08%2Frefueling-an-ev-be-like%2F&amp;dp=%2F2026%2F10%2F08%2Frefueling-an-ev-be-like%2F&amp;dt=Refueling%20an%20EV%20be%20like&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2026/10/08/refueling-an-ev-be-like/&quot;&gt;https://blog.tomayac.com/2026/10/08/refueling-an-ev-be-like/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Guest blog post on Cross-Origin Storage in Transformers.js</title>
		<link href="https://blog.tomayac.com/2026/06/24/guest-blog-post-on-cross-origin-storage-in-transformersjs/"/>
		<updated>2026-06-24T12:24:03Z</updated>
		<id>https://blog.tomayac.com/2026/06/24/guest-blog-post-on-cross-origin-storage-in-transformersjs/</id>
		<content type="html">
			&lt;p&gt;I&#39;m very excited to share a guest post I wrote for the Hugging Face 🤗 blog! The
post is called
👉 &lt;a href=&quot;https://huggingface.co/blog/cross-origin-storage&quot;&gt;Experimenting with the proposed Cross-Origin Storage API in Transformers.js&lt;/a&gt; 👈!
This proposed new browser API,
&lt;code&gt;navigator.crossOriginStorage.getFileHandle(hash)&lt;/code&gt;, has the potential of
revolutionizing the Web, a little bit at least. Learn more by reading the
&lt;a href=&quot;https://github.com/WICG/cross-origin-storage&quot;&gt;Explainer for the Cross-Origin Storage (COS) API&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And Transformers.js aren&#39;t alone with experimenting with this, they&#39;re joined by
&lt;a href=&quot;https://webllm.mlc.ai/docs/user/advanced_usage.html#using-cross-origin-storage-cache&quot;&gt;WebLLM&lt;/a&gt;
(docs),
&lt;a href=&quot;https://github.com/ngxson/wllama/blob/master/src/storage/cos.ts&quot;&gt;wllama&lt;/a&gt;
(code),
&lt;a href=&quot;https://github.com/flutter/flutter/blob/master/engine/src/flutter/lib/web_ui/flutter_js/src/instantiate_wasm.js&quot;&gt;Flutter&lt;/a&gt;
(code), and
&lt;a href=&quot;https://emscripten.org/docs/compiling/CrossOriginStorage.html#crossoriginstorage&quot;&gt;Emscripten&lt;/a&gt;
(docs).&lt;/p&gt;
&lt;p&gt;In a nutshell, this is the usual flow of using the API:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hash &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;algorithm&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;SHA-256&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crossOriginStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Cache hit! Get the file as a Blob and use it directly.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileBlob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// Cache miss. Download from network, then store for next time.&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileBlob &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;https://cdn.jsdelivr.net/.../ort-wasm-simd-threaded.asyncify.wasm&#39;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; handle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; navigator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;crossOriginStorage&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getFileHandle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hash&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;origins&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; writableStream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; handle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createWritable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fileBlob&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; writableStream&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/cos.svg&quot; alt=&quot;The Cross-Origin Storage API logo: a stylized walking person, as typically encountered on crosswalk signs.&quot; /&gt;&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2026%2F06%2F24%2Fguest-blog-post-on-cross-origin-storage-in-transformersjs%2F&amp;dp=%2F2026%2F06%2F24%2Fguest-blog-post-on-cross-origin-storage-in-transformersjs%2F&amp;dt=Guest%20blog%20post%20on%20Cross-Origin%20Storage%20in%20Transformers.js&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2026/06/24/guest-blog-post-on-cross-origin-storage-in-transformersjs/&quot;&gt;https://blog.tomayac.com/2026/06/24/guest-blog-post-on-cross-origin-storage-in-transformersjs/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Your MP3s from Google Drive in Music Assistant on Home Assistant</title>
		<link href="https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/"/>
		<updated>2026-05-30T15:28:59Z</updated>
		<id>https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/</id>
		<content type="html">
			&lt;p&gt;Like most millennials, I grew up with
&lt;a href=&quot;https://en.wikipedia.org/wiki/Napster&quot;&gt;Napster&lt;/a&gt;, then
&lt;a href=&quot;https://en.wikipedia.org/wiki/Kazaa&quot;&gt;Kazaa&lt;/a&gt;, then
&lt;a href=&quot;https://en.wikipedia.org/wiki/EDonkey2000&quot;&gt;eDonkey2000&lt;/a&gt;, obviously
&lt;a href=&quot;https://en.wikipedia.org/wiki/Ripping&quot;&gt;CD rips&lt;/a&gt;, and of course
&lt;a href=&quot;https://en.wikipedia.org/wiki/LAN_party&quot;&gt;LAN parties&lt;/a&gt;. Over the years, this has
led to a large collection of completely legal MP3 files that I have backed up on
two large external USB spinning disks that haven&#39;t spun for a long time now, but
the files also exist on Google Drive.&lt;/p&gt;
&lt;p&gt;I&#39;m also heavily invested in the
&lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt; ecosystem, which lets you
&lt;a href=&quot;https://my.home-assistant.io/redirect/supervisor_addon/?addon=d5369777_music_assistant&amp;amp;repository_url=https%3A%2F%2Fgithub.com%2Fmusic-assistant%2Fhome-assistant-addon&quot;&gt;install&lt;/a&gt;
a full-featured music player app called
&lt;a href=&quot;https://www.music-assistant.io/&quot;&gt;Music Assistant&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/homeassistant.png&quot; alt=&quot;My Home Assistant dashboard.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;These days, of course I pay for my music (we have the YouTube Music family
plan), but I recently thought it&#39;d be fun to revive my old MP3 collection. This
blog post describes how to connect Google Drive to Music Assistant on Home
Assistant.&lt;/p&gt;
&lt;h2 id=&quot;create-a-rclone-configuration-for-google-drive&quot; tabindex=&quot;-1&quot;&gt;Create a rclone configuration for Google Drive &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/#create-a-rclone-configuration-for-google-drive&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first step is running &lt;a href=&quot;https://rclone.org/&quot;&gt;rclone&lt;/a&gt;, a command-line program
to manage files on cloud storage. Among many other providers, it supports Google
Drive. Download rclone to your regular computer (that is, very likely, not your
Home Assistant), and then follow the instructions to
&lt;a href=&quot;https://rclone.org/drive/&quot;&gt;configure rclone for Google Drive&lt;/a&gt;. This requires
you to go through the hell that is Google&#39;s Cloud Console, but it&#39;s a one-time
setup. All you need in the end is a configuration file that you then have to
copy over to your Home Assistant. Essentially, you run the configuration wizard,
show the configuration file, and then copy its contents.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;rclone config
rclone config show&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should look something like the following snippet, but of course with your own
client ID, client secret, and token:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;drive&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; drive
client_id &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; *.apps.googleusercontent.com
client_secret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; *-*
scope &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; drive
token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;access_token&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;token_type&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bearer&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;refresh_token&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;expiry&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;,&lt;span class=&quot;token string&quot;&gt;&quot;expires_in&quot;&lt;/span&gt;:3599&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
team_drive &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In Home Assistant, use the
&lt;a href=&quot;https://github.com/home-assistant/addons/tree/master/configurator&quot;&gt;File editor&lt;/a&gt;
addon (app) and create a file called &lt;code&gt;rclone.conf&lt;/code&gt; in the &lt;code&gt;config/rclone&lt;/code&gt;
directory. Paste the contents of your rclone configuration file into it and save
it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/rclone-config.png&quot; alt=&quot;The rclone configuration file in the Home Assistant File editor.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;set-up-rclone-in-home-assistant&quot; tabindex=&quot;-1&quot;&gt;Set up rclone in Home Assistant &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/#set-up-rclone-in-home-assistant&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you have the rclone configuration file in place, you need to set up
rclone to serve your Google Drive as a WebDAV server. Install the
&lt;a href=&quot;https://github.com/hassio-addons/app-ssh&quot;&gt;Advanced SSH &amp;amp; Web Terminal&lt;/a&gt; app in
Home Assistant. Open its &lt;strong&gt;Configuration&lt;/strong&gt; tab and where it says &lt;strong&gt;Options&lt;/strong&gt;,
click the three dot menu and select &lt;strong&gt;Edit in YAML&lt;/strong&gt;. Add the following
configuration to it, which will install rclone and then start a WebDAV server on
port 8080 that serves your Google Drive. This will be run automatically each
time you start Home Assistant.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;ssh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hassio
  &lt;span class=&quot;token key atrule&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; passw0rd &lt;span class=&quot;token comment&quot;&gt;# ⚠️ Change this to your own password!&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;authorized_keys&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;sftp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;compatibility_mode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;allow_agent_forwarding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;allow_remote_port_forwarding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;allow_tcp_forwarding&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;zsh&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;share_sessions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key atrule&quot;&gt;packages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; rclone
&lt;span class=&quot;token key atrule&quot;&gt;init_commands&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;rclone serve webdav drive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;addr &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;8080 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;baseurl /google_music &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config
    /config/rclone/rclone.conf &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;vfs&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cache&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;mode full &amp;amp;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/advanced-ssh.png&quot; alt=&quot;The Advanced SSH &amp;amp; Web Terminal configuration in Home Assistant.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;connect-music-assistant-to-the-rclone-webdav-server&quot; tabindex=&quot;-1&quot;&gt;Connect Music Assistant to the rclone WebDAV server &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/#connect-music-assistant-to-the-rclone-webdav-server&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Finally, all you need to do is install
&lt;a href=&quot;https://www.music-assistant.io/&quot;&gt;Music Assistant&lt;/a&gt; and connect it to the WebDAV
server that rclone is running. In Home Assistant, open the Music Assistant app
and go to the &lt;strong&gt;Settings&lt;/strong&gt; section. Click the &lt;strong&gt;Music sources&lt;/strong&gt; button to add a
new integration, and select &lt;strong&gt;WebDAV Provider&lt;/strong&gt; from the list. Add
&lt;code&gt;http://127.0.0.1:8080/google_music&lt;/code&gt; as the WebDAV URL, and enter your username
and password.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/musicassistant.png&quot; alt=&quot;Adding a WebDAV music source in Music Assistant&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;press-play&quot; tabindex=&quot;-1&quot;&gt;Press play &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/#press-play&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If all went well, you should now see your Google Drive music collection in Music
Assistant and can browse it. Some will be embarrassing, some will have aged
poorly, and some will bring back fond memories and be timeless. Enjoy the
nostalgia!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/musicassistant-player.png&quot; alt=&quot;A song playing in Music Assistant.&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;a-personal-note&quot; tabindex=&quot;-1&quot;&gt;A personal note &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/#a-personal-note&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you like this blog post, please consider sharing it. Let&#39;s keep personal
blogging alive! This post was hand-written by me with love and a cup of coffee,
weird non-native English and all, hitting &lt;kbd&gt;Esc&lt;/kbd&gt; a thousand times to
keep the AI assistance away. If the post helped you, I always appreciate it if
you &lt;a href=&quot;https://blog.tomayac.com/about/#email&quot;&gt;let me know&lt;/a&gt;.&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2026%2F05%2F30%2Fyour-mp3s-from-google-drive-in-music-assistant-on-home-assistant%2F&amp;dp=%2F2026%2F05%2F30%2Fyour-mp3s-from-google-drive-in-music-assistant-on-home-assistant%2F&amp;dt=Your%20MP3s%20from%20Google%20Drive%20in%20Music%20Assistant%20on%20Home%20Assistant&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/&quot;&gt;https://blog.tomayac.com/2026/05/30/your-mp3s-from-google-drive-in-music-assistant-on-home-assistant/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>A polyfill for the HTML switch element</title>
		<link href="https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/"/>
		<updated>2026-01-12T09:21:24Z</updated>
		<id>https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/</id>
		<content type="html">
			&lt;p&gt;In Safari 17.4, the WebKit team at Apple
&lt;a href=&quot;https://webkit.org/blog/15054/an-html-switch-control/&quot;&gt;shipped a native HTML switch element&lt;/a&gt;.
The core idea is that an &lt;code&gt;&amp;lt;input type=&amp;quot;checkbox&amp;quot;&amp;gt;&lt;/code&gt; can progressively be enhanced
to become a switch by adding the &lt;code&gt;switch&lt;/code&gt; attribute. Browsers that don&#39;t support
the &lt;code&gt;switch&lt;/code&gt; attribute will just silently ignore it and render the switch as a
regular checkbox. At the time of this writing, Safari version 17.4 and later is
the only browser to support the new switch element natively. This blog post
introduces a &lt;strong&gt;polyfill&lt;/strong&gt; that brings &lt;em&gt;almost&lt;/em&gt; native support to browsers that
lack it.&lt;/p&gt;
&lt;p&gt;The markup below shows you how you use the switch element. If your browser
doesn&#39;t support the element natively and you view this page on my blog directly
(that is, not in your feed reader), the polyfill should have already kicked in
and you should see two switch controls below the code sample: one regular
switch, and one with a red
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/accent-color&quot;&gt;&lt;code&gt;accent-color&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Toggle me &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;checkbox&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;checked&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token style&quot;&gt;&lt;span class=&quot;token language-css&quot;&gt;
  &lt;span class=&quot;token selector&quot;&gt;.special&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;accent-color&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; red&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Toggle me, I&#39;m special &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;checkbox&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;checked&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;special&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;style&gt;
  @keyframes hideBriefly {
    0%,
    100% {
      visibility: hidden;
    }
  }

  label:has(input[switch]),
  input[switch] {
    animation: hideBriefly 2s;
  }

  label {
    font-weight: bold;
    user-select: none;
  }
&lt;/style&gt;
&lt;noscript&gt;
  &lt;style&gt;
    label:has(input[switch]),
    input[switch] {
      animation: none;
    }
  &lt;/style&gt;
&lt;/noscript&gt;
&lt;script type=&quot;module&quot;&gt;
  if (!(&#39;switch&#39; in HTMLInputElement.prototype))
    await import(&#39;/js/input-switch-polyfill.js&#39;);
  else 
    document.head.insertAdjacentHTML(&#39;beforeend&#39;, &#39;&lt;style&gt;label:has(input[switch]),input[switch]{animation:none!important}&lt;/style&gt;&#39;);
&lt;/script&gt;
&lt;label&gt;Toggle me &lt;input type=&quot;checkbox&quot; switch=&quot;&quot; checked=&quot;&quot; /&gt;&lt;/label&gt;
&lt;style&gt;
  .special {
    accent-color: red;
  }
&lt;/style&gt;
&lt;p&gt;&lt;label&gt;Toggle me, I&#39;m special
&lt;input type=&quot;checkbox&quot; switch=&quot;&quot; checked=&quot;&quot; class=&quot;special&quot; /&gt;&lt;/label&gt;&lt;/p&gt;
&lt;h2 id=&quot;accessibility&quot; tabindex=&quot;-1&quot;&gt;Accessibility &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/#accessibility&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If a checkbox becomes a switch, the browser automatically applies the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/switch_role&quot;&gt;ARIA &lt;code&gt;switch&lt;/code&gt;&lt;/a&gt;
role. This role is functionally identical to the &lt;code&gt;checkbox&lt;/code&gt; role, except that
instead of representing &amp;quot;checked&amp;quot; and &amp;quot;unchecked&amp;quot; states, which are fairly
generic in meaning, the switch role represents the states &amp;quot;on&amp;quot; and &amp;quot;off&amp;quot;. The
polyfill does this for you. Also another exception is that switches don&#39;t
support an indeterminate/mixed state that checkboxes support.&lt;/p&gt;
&lt;p&gt;When your users have the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-contrast&quot;&gt;&lt;code&gt;prefers-contrast&lt;/code&gt;&lt;/a&gt;
setting enabled to convey that they prefer more contrast, the polyfill adds more
visible borders. Some operating systems like Windows or browsers like Firefox
additionally support a high contrast mode. The polyfill also has support for
that.&lt;/p&gt;
&lt;p&gt;The macOS operating system additionally has an accessibility setting to
&lt;a href=&quot;https://developer.apple.com/help/app-store-connect/manage-app-accessibility/differentiate-without-color-alone-evaluation-criteria/&quot;&gt;Differentiate without color&lt;/a&gt;,
which causes switch controls to get rendered with additional visual on/off
indicators. Since there is currently no direct CSS media query for this specific
preference, I opted to display these indicators whenever a high-contrast
preference is detected, ensuring maximum clarity for those who need it.&lt;/p&gt;
&lt;p&gt;A common accessibility challenge with switches
&lt;a href=&quot;https://www.cs.umd.edu/hcil/trs/90-08/90-08.pdf&quot;&gt;identified in research&lt;/a&gt; (that
predates the HTML switch control) is an uncertainty whether the user should tap
or slide the switch to change its state. The polyfill, like the native
counterpart in Safari, supports both. Another challenge is whether the label
&amp;quot;on&amp;quot; indicates the current state of the switch or the resulting state after
interacting with it. I personally think smartphones—most notably the iPhone—have
taught people how to use switches, but I still recommend you do your own
usability research before adding a switch to your site.&lt;/p&gt;
&lt;h2 id=&quot;internationalization-and-styling&quot; tabindex=&quot;-1&quot;&gt;Internationalization and styling &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/#internationalization-and-styling&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The polyfill supports the various
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/writing-mode&quot;&gt;&lt;code&gt;writing-mode&lt;/code&gt;&lt;/a&gt;
options, like &lt;code&gt;&amp;quot;vertical-lr&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;label style=&quot;writing-mode: vertical-lr&quot;&gt;I&#39;m vertical
&lt;input type=&quot;checkbox&quot; switch=&quot;&quot; checked=&quot;&quot; /&gt;&lt;/label&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s also aware of the directionality of text via the
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/dir&quot;&gt;&lt;code&gt;dir&lt;/code&gt;&lt;/a&gt;
attribute.&lt;/p&gt;
&lt;p&gt;&lt;label dir=&quot;rtl&quot;&gt;I go from right to left
&lt;input type=&quot;checkbox&quot; switch=&quot;&quot; checked=&quot;&quot; /&gt;&lt;/label&gt;&lt;/p&gt;
&lt;h2 id=&quot;status-in-html&quot; tabindex=&quot;-1&quot;&gt;Status in HTML &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/#status-in-html&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The switch element was proposed to be included in HTML in
&lt;a href=&quot;https://github.com/whatwg/html/issues/4180&quot;&gt;Issue #4180&lt;/a&gt; filed in
November 2018. &lt;a href=&quot;https://github.com/whatwg/html/pull/9546&quot;&gt;PR #9546&lt;/a&gt; (opened in
July 2023) proposed a fix and was
&lt;a href=&quot;https://github.com/whatwg/html/pull/9546#pullrequestreview-1584169867&quot;&gt;approved&lt;/a&gt;
by &lt;a href=&quot;https://annevankesteren.nl/&quot;&gt;Anne van Kesteren&lt;/a&gt; in August 2023. At the time
of this writing, the PR to the HTML spec is still open, with concerns from
several stakeholders, including from Google.&lt;/p&gt;
&lt;p&gt;I am not and was not part of the standardization discussion around the element,
I just personally like the progressive enhancement pattern that reminds me of
the pattern used in
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Customizable_select&quot;&gt;customizable &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; elements&lt;/a&gt;
that in the case of non-support just get rendered as regular selects.&lt;/p&gt;
&lt;h2 id=&quot;get-the-polyfill&quot; tabindex=&quot;-1&quot;&gt;Get the polyfill &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/#get-the-polyfill&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can
&lt;a href=&quot;https://www.npmjs.com/package/input-switch-polyfill&quot;&gt;get the polyfill from npm&lt;/a&gt;
and &lt;a href=&quot;https://github.com/tomayac/input-switch-polyfill&quot;&gt;find the code on GitHub&lt;/a&gt;.
The
&lt;a href=&quot;https://github.com/tomayac/input-switch-polyfill/blob/main/README.md&quot;&gt;&lt;code&gt;README&lt;/code&gt;&lt;/a&gt;
has detailed usage instructions that I won&#39;t repeat here, including &lt;strong&gt;important
tips on how to avoid FOUC&lt;/strong&gt; (Flash of Unstyled Content). You can also play with
a &lt;a href=&quot;https://tomayac.github.io/input-switch-polyfill/&quot;&gt;demo&lt;/a&gt; of the polyfill that
shows off more features of the polyfill, like all the various writing modes, and
the different ways to style the switch. And with that: happy switching!&lt;/p&gt;
&lt;h2 id=&quot;thank-you&quot; tabindex=&quot;-1&quot;&gt;Thank you &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/#thank-you&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would like to wholeheartedly thank
&lt;a href=&quot;https://fed.interfree.ca/@fastfinge&quot;&gt;Samuel Proulx&lt;/a&gt; and
&lt;a href=&quot;https://c.im/@cwilcox808&quot;&gt;Curtis Wilcox&lt;/a&gt; for their accessibility advice and
testing. I&#39;m also grateful to
&lt;a href=&quot;https://mastodon.social/@pepelsbey&quot;&gt;Vadim Makeev&lt;/a&gt;,
&lt;a href=&quot;https://toot.wales/@Lukew&quot;&gt;Luke Warlow&lt;/a&gt;, and
&lt;a href=&quot;https://hachyderm.io/@jyasskin&quot;&gt;Jeffrey Yasskin&lt;/a&gt; for their technical and
non-technical feedback. &lt;a href=&quot;https://piaille.fr/@tbroyer&quot;&gt;Thomas Broyer&lt;/a&gt; has been
crucial for improving support on Firefox.
&lt;a href=&quot;https://front-end.social/@bramus&quot;&gt;Bramus Van Damme&lt;/a&gt; pointed me at Richard
Keizer&#39;s &lt;a href=&quot;https://codepen.io/rakeizer/pen/QQRBoZ&quot;&gt;CodePen&lt;/a&gt; that helped me
bootstrap the CSS foundations of the polyfill. Finally, huge props to
&lt;a href=&quot;https://mastodon.social/@tunetheweb&quot;&gt;Barry Pollard&lt;/a&gt; for the performance tweaks
to avoid FOUC.&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2026%2F01%2F12%2Fa-polyfill-for-the-html-switch-element%2F&amp;dp=%2F2026%2F01%2F12%2Fa-polyfill-for-the-html-switch-element%2F&amp;dt=A%20polyfill%20for%20the%20HTML%20switch%20element&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/&quot;&gt;https://blog.tomayac.com/2026/01/12/a-polyfill-for-the-html-switch-element/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Using the Web Monetization API for fun and profit</title>
		<link href="https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/"/>
		<updated>2025-11-07T22:10:50Z</updated>
		<id>https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/</id>
		<content type="html">
			&lt;p&gt;I recently &lt;a href=&quot;https://jsconfmx.org/speaker/W3Eio&quot;&gt;spoke at JSConf Mexico&lt;/a&gt;, where I
spent a lot of time with the &lt;a href=&quot;https://interledger.org/&quot;&gt;Interledger Foundation&lt;/a&gt;
folks in the hallway track and at the after party events, namely with
&lt;a href=&quot;https://www.linkedin.com/in/ioanachiorean/&quot;&gt;Ioana&lt;/a&gt; (Engineering Manager) and
&lt;a href=&quot;https://www.linkedin.com/in/marianvilla/&quot;&gt;Marian&lt;/a&gt; (DevRel) to talk about
&lt;a href=&quot;https://webmonetization.org/&quot;&gt;Web Monetization&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web Monetization gives publishers more revenue options and audiences more ways
to sustain the content they love. Support can take many forms: from a one-time
contribution to a continuous, pay-as-you-browse model. It all flows seamlessly
while people engage with the content they love. Publishers earn the moment
someone engages, while audiences contribute in real time, using a balance they
control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I encourage you all to give it a try! Install the
&lt;a href=&quot;https://chromewebstore.google.com/detail/web-monetization/oiabcfomehhigdepbbclppomkhlknpii&quot;&gt;extension&lt;/a&gt;
that polyfills the proposed Web standard, get a wallet (I went with
&lt;a href=&quot;https://gatehub.net/&quot;&gt;GateHub&lt;/a&gt;, which works in US Dollars and Euros), and then
connect it to the extension.&lt;/p&gt;
&lt;p&gt;You need to have funds in EUR (€) or USD ($). If you have crypto, it won&#39;t work,
which I&#39;ve found out by trial and error, as I was part of
&lt;a href=&quot;https://coil.com/&quot;&gt;Coil&lt;/a&gt;, the Web Monetization predecessor, which paid out in
XRP.&lt;/p&gt;
&lt;p&gt;Just to clarify, while you need a wallet—that typically is used for crypto—the
actual transactions are all in real fiat money, Euro in my case.&lt;/p&gt;
&lt;h2 id=&quot;as-an-extension-user&quot; tabindex=&quot;-1&quot;&gt;As an extension user &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/#as-an-extension-user&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Connect your wallet and browse to a page that supports Web Monetization. You
will notice whether a page is monetized when the extension has a green
checkmark. My blog happens to be monetized.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/web-monetization-extension.png&quot; alt=&quot;The Web Monetization extensions&#39;s popup window.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can adjust how much you want to pay the site per hour and also send one-time
payments. The money is &amp;quot;streamed&amp;quot; every minute, which you can observe in
DevTools.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/web-monetization-post-request.png&quot; alt=&quot;Chrome DevTools Network tab showing a POST request for a payment.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We actually have
&lt;a href=&quot;https://groups.google.com/a/chromium.org/d/msgid/blink-dev/d91487a5-108c-46e7-accd-d44b734a0b34%40igalia.com&quot;&gt;code in Chromium&lt;/a&gt;
to make native Web Monetization happen, implemented by Igalia and funded by the
Interledger Foundation. I hope they can share the experiment results soon.&lt;/p&gt;
&lt;h2 id=&quot;as-a-publisher&quot; tabindex=&quot;-1&quot;&gt;As a publisher &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/#as-a-publisher&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;On your page, add a
&lt;a href=&quot;https://webmonetization.org/developers/link-element/&quot;&gt;payment link&lt;/a&gt;. You get
the personalized payment pointer from your wallet. The following snippet shows
mine.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;monetization&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://ilp.gatehub.net/348218105/eur&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you&#39;re ready to receive payments. Here&#39;s me browsing my blog and seeing
payments go out from and come in to my GateHub wallet. This is of course
effectively a zero sum game, me paying myself. The 0.01 cent are the streamed
payments that go out and then come in again. I tested a one-time payment as
well. The 0.50 cents (not shown) was a successful one-time payment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/web-monetization-wallet.png&quot; alt=&quot;The GateHub wallet showing incoming and outgoing transactions.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s also a
&lt;a href=&quot;https://webmonetization.org/developers/interfaces/&quot;&gt;JavaScript API&lt;/a&gt;, so you can
adjust the content of your page when your page notices that the user is paying.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;monetization&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; currency &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;amountSent&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Browser sent &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;currency&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;value&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; linkElem &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;for link element:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; linkElem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; linkElem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;href&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For testing purposes, you can observe these &lt;code&gt;monetization&lt;/code&gt; events in Chrome
DevTools by pasting in the snippet above in the Console.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/web-monetization-devtools.png&quot; alt=&quot;Chrome DevTools Console showing a monetization event.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This way you could, for example, remove ads, or unlock an article when you
notice a one-time payment. On my blog, I just show a &amp;quot;thank you&amp;quot; message for
now.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/web-monetization-thank-you.png&quot; alt=&quot;Thank you message in the footer of my blog showing how much the user has paid.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m really bulli$h on this proposed standard. Hopefully someone else will try it
and let me know how it goes. I truly and honestly believe that this could be the
future for making the Web of tomorrow financially sustainable for publishers,
big and small.&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F11%2F07%2Fusing-the-web-monetization-api-for-fun-and-profit%2F&amp;dp=%2F2025%2F11%2F07%2Fusing-the-web-monetization-api-for-fun-and-profit%2F&amp;dt=Using%20the%20Web%20Monetization%20API%20for%20fun%20and%20profit&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/&quot;&gt;https://blog.tomayac.com/2025/11/07/using-the-web-monetization-api-for-fun-and-profit/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Running Node.js in a Hugging Face Space</title>
		<link href="https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/"/>
		<updated>2025-11-03T06:19:54Z</updated>
		<id>https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/</id>
		<content type="html">
			&lt;p&gt;Like many developers, I was bummed when I learned about the
&lt;a href=&quot;https://blog.glitch.com/post/changes-are-coming-to-glitch&quot;&gt;shutdown of Glitch&lt;/a&gt;.
While &lt;a href=&quot;https://pages.github.com/&quot;&gt;GitHub Pages&lt;/a&gt; works great for web apps that
don&#39;t need a server, I struggled with finding a drop-in replacement for hosting
server-based apps, and specifically apps using Node.js. Until I found out about
&lt;a href=&quot;https://huggingface.co/docs/hub/spaces&quot;&gt;Hugging Face Spaces&lt;/a&gt; and that it
supports Docker, which allowed me to create an &lt;strong&gt;evergreen template for running
Node.js in a Hugging Face Space&lt;/strong&gt;.&lt;/p&gt;
&lt;div style=&quot;text-align: center&quot;&gt;
  &lt;img src=&quot;https://blog.tomayac.com/images/huggingface-logo.svg&quot; width=&quot;95&quot; height=&quot;88&quot; alt=&quot;Hugging Face&quot; /&gt;
  &lt;span style=&quot;font-size: 4rem; transform: translateY(-10px); display: inline-block;&quot;&gt;♥️&lt;/span&gt;
  &lt;img src=&quot;https://blog.tomayac.com/images/nodejs-mascot.svg&quot; width=&quot;680&quot; height=&quot;800&quot; style=&quot;height: 88px; width: auto;&quot; alt=&quot;Node.js&quot; /&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;If all you want is a quick way to fire up your own Space-hosted Node.js
server, click
&lt;a href=&quot;https://huggingface.co/spaces/tomayac/nodejs-template?duplicate=true&quot;&gt;Duplicate this Space&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you want to know how the sausage is made or create your own template, read
on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;create-a-hugging-face-space&quot; tabindex=&quot;-1&quot;&gt;Create a Hugging Face Space &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#create-a-hugging-face-space&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This assumes that you have a (free or paid)
&lt;a href=&quot;https://huggingface.co/join&quot;&gt;account on Hugging Face&lt;/a&gt;. Go to your profile and
&lt;a href=&quot;https://huggingface.co/new-space&quot;&gt;create a new Hugging Face Space&lt;/a&gt; using
&lt;strong&gt;Docker&lt;/strong&gt; as the Space SDK. Go for the &lt;strong&gt;Blank&lt;/strong&gt; Docker template. Leave all the
other settings unchanged, so you end up on the free tier. Choose if your Space
should be private or public.&lt;/p&gt;
&lt;h2 id=&quot;an-evergreen-template&quot; tabindex=&quot;-1&quot;&gt;An evergreen template &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#an-evergreen-template&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The objective is to make this template evergreen, so no concrete version numbers
are hardwired. Instead, the idea is to hardwire the version numbers when you
duplicate the template to create a permanent Space.&lt;/p&gt;
&lt;h3 id=&quot;create-a-package-json-file&quot; tabindex=&quot;-1&quot;&gt;Create a &lt;code&gt;package.json&lt;/code&gt; file &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#create-a-package-json-file&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Next, create the &lt;code&gt;package.json&lt;/code&gt; file that your template should use. Note that
this uses &lt;code&gt;&amp;quot;latest&amp;quot;&lt;/code&gt; as the Express.js version, as the template is meant to stay
evergreen.&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nodejs-template&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A template for running Node.js in a Hugging Face Space.&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;keywords&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Node&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Node.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hugging Face Space&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;repository&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git@hf.co:spaces/tomayac/nodejs-template&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Apache-2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Thomas Steiner (tomac@google.com)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;module&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;index.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;start&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node index.js&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;express&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;latest&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;create-a-dockerfile&quot; tabindex=&quot;-1&quot;&gt;Create a &lt;code&gt;Dockerfile&lt;/code&gt; &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#create-a-dockerfile&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As the next step, create a &lt;code&gt;Dockerfile&lt;/code&gt; for your template. Again I&#39;m using an
evergreen approach here with a Node.js Docker tag of &lt;code&gt;lts-alpine&lt;/code&gt;, which means I
always get the LTS release of Node.js running on the lightweight Alpine Linux.&lt;/p&gt;
&lt;pre class=&quot;language-docker&quot;&gt;&lt;code class=&quot;language-docker&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Node base image&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; node:lts-alpine&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Switch to the &quot;node&quot; user&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;USER&lt;/span&gt; node&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Set home to the user&#39;s home directory&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENV&lt;/span&gt; HOME=/home/node PATH=/home/node/.local/bin:&lt;span class=&quot;token variable&quot;&gt;$PATH&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Set the working directory to the user&#39;s home directory&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$HOME&lt;/span&gt;/app&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Moving file to user&#39;s home directory&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ADD&lt;/span&gt; . &lt;span class=&quot;token variable&quot;&gt;$HOME&lt;/span&gt;/app&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Copy the current directory contents into the container at $HOME/app setting the owner to the user&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; &lt;span class=&quot;token options&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--chown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;node&lt;/span&gt;&lt;/span&gt; . &lt;span class=&quot;token variable&quot;&gt;$HOME&lt;/span&gt;/app&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Loading Dependencies&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; npm install&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Expose application&#39;s default port&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPOSE&lt;/span&gt; 7860&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# Entry Point&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;nodejs&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;./index.js&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;create-an-index-js-file&quot; tabindex=&quot;-1&quot;&gt;Create an &lt;code&gt;index.js&lt;/code&gt; file &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#create-an-index-js-file&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Up next, create your default &lt;code&gt;index.js&lt;/code&gt; file that your template should use when
the Node.js server starts. I went with the battle-proven Express.js server
framework. Note that the port needs to be &lt;code&gt;7860&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now for the smart part: The code dynamically reads out the used Express.js and
Node.js version, so when you duplicate the template, you can hard-wire these
versions. After duplicating the template, in your code, update the highlighted
parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In your &lt;code&gt;Dockerfile&lt;/code&gt;, replace &lt;code&gt;node:&lt;strong&gt;lts&lt;/strong&gt;-alpine&lt;/code&gt;
with, for example, &lt;code&gt;node:&lt;strong&gt;24&lt;/strong&gt;-alpine&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In your &lt;code&gt;package.json&lt;/code&gt; file, replace &lt;code&gt;&amp;quot;express&amp;quot;:
&amp;quot;&lt;strong&gt;latest&lt;/strong&gt;&amp;quot;&lt;/code&gt; with, for example, &lt;code&gt;&amp;quot;express&amp;quot;:
&amp;quot;^&lt;strong&gt;5.1.0&lt;/strong&gt;&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; express &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;express&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;express&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; port &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;7860&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Running Express.js &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;express/package.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token keyword&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version
    &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; on Node.js &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;version&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;v&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&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;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;port&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Example app listening on port &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;port&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;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;create-a-reamde-md-file&quot; tabindex=&quot;-1&quot;&gt;Create a &lt;code&gt;REAMDE.md&lt;/code&gt; file &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#create-a-reamde-md-file&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To set some metadata for your template, create a &lt;code&gt;README.md&lt;/code&gt; file with YAML
front matter at the beginning. Hugging Face makes this easy via its Web
interface for the standard parameters, but you can modify
&lt;a href=&quot;https://huggingface.co/docs/hub/spaces-config-reference&quot;&gt;many more parameters&lt;/a&gt;
as per the documentation.&lt;/p&gt;
&lt;pre class=&quot;language-md&quot;&gt;&lt;code class=&quot;language-md&quot;&gt;&lt;span class=&quot;token front-matter-block&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;token front-matter yaml language-yaml&quot;&gt;license: apache-2.0
title: Node.js template
sdk: docker
emoji: 🐢
colorFrom: green
colorTo: green
short_description: A template for running Node.js in a Hugging Face Space&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;whats-missing&quot; tabindex=&quot;-1&quot;&gt;What&#39;s missing? &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#whats-missing&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While you can edit files individually on Hugging Face&#39;s Space
&lt;a href=&quot;https://huggingface.co/spaces/tomayac/nodejs-template/tree/main&quot;&gt;Files&lt;/a&gt; view
with syntax highlighting and editing support, it&#39;s not a full-blown IDE, but you
can clone your Space with &lt;code&gt;git&lt;/code&gt; and work on it locally (or with an online IDE
like &lt;a href=&quot;https://vscode.dev/&quot;&gt;VS Code&lt;/a&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone git@hf.co:spaces/tomayac/nodejs-template&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;see-it-live-and-bonus&quot; tabindex=&quot;-1&quot;&gt;See it live and bonus &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/#see-it-live-and-bonus&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;And this is it really. Now you have a running Node.js app that you can
&lt;a href=&quot;https://huggingface.co/spaces/tomayac/nodejs-template?duplicate=true&quot;&gt;duplicate&lt;/a&gt;
whenever you need to spin up a Node.js server. The best is that this Space runs
in its own main browser context,
&lt;a href=&quot;https://tomayac-nodejs-template.hf.space/&quot;&gt;&lt;code&gt;https://tomayac-nodejs-template.hf.space/&lt;/code&gt;&lt;/a&gt;
in the concrete case, not somewhere in an iframe, which means you can set
headers like
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy&quot;&gt;&lt;code&gt;COOP&lt;/code&gt;&lt;/a&gt;
or
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Embedder-Policy&quot;&gt;&lt;code&gt;COEP&lt;/code&gt;&lt;/a&gt;
to get access to powerful features like
&lt;a href=&quot;https://web.dev/articles/why-coop-coep&quot;&gt;&lt;code&gt;SharedArrayBuffer&lt;/code&gt; and friends&lt;/a&gt;. In
fact, Hugging Face even allows you to set these
&lt;a href=&quot;https://huggingface.co/docs/hub/spaces-config-reference#:~:text=)%20is%20acceptable.-,custom_headers,-%3A%20Dict%5Bstring&quot;&gt;&lt;code&gt;custom_headers&lt;/code&gt;&lt;/a&gt;
by default in the YAML front matter config at the beginning of the &lt;code&gt;README.md&lt;/code&gt;.
Note, though, that adding these headers means your app will only run in
standalone mode, but no longer in the default Space iframed view.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;custom_headers&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;cross-origin-embedder-policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; require&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;corp
  &lt;span class=&quot;token key atrule&quot;&gt;cross-origin-opener-policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; same&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;origin
  &lt;span class=&quot;token key atrule&quot;&gt;cross-origin-resource-policy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; cross&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;origin&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy hacking!&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F11%2F03%2Frunning-nodejs-in-a-hugging-face-space%2F&amp;dp=%2F2025%2F11%2F03%2Frunning-nodejs-in-a-hugging-face-space%2F&amp;dt=Running%20Node.js%20in%20a%20Hugging%20Face%20Space&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/&quot;&gt;https://blog.tomayac.com/2025/11/03/running-nodejs-in-a-hugging-face-space/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>Prompt API color sensitivity</title>
		<link href="https://blog.tomayac.com/2025/09/16/prompt-api-color-sensitivity/"/>
		<updated>2025-09-09T16:37:09Z</updated>
		<id>https://blog.tomayac.com/2025/09/16/prompt-api-color-sensitivity/</id>
		<content type="html">
			&lt;p&gt;I was playing with stress-testing the
&lt;a href=&quot;https://developer.chrome.com/docs/ai/prompt-api#multimodal_capabilities&quot;&gt;multimodal capabilities of the Prompt API&lt;/a&gt;
and thought a nice test case might be to have the model read the current time
painted on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;. As with my
&lt;a href=&quot;https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/#bonus&quot;&gt;last Prompt API exploration&lt;/a&gt;,
I&#39;m again using a response constraint, the &lt;code&gt;HH:mm:ss&lt;/code&gt; regular expression
&lt;code&gt;/^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/&lt;/code&gt;. The prompt is &lt;em&gt;&amp;quot;Read the
time that you can see in this image and print it in HH:mm:ss format.&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To my surprise, the model (Gemini Nano in Chrome) seems to be quite
color-sensitive. I found that the model often gets the time wrong in dark mode
when a red font is used to paint on the canvas. (The
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/system-color#:~:text=color%20of%20controls.-,Canvas,-Background%20of%20application&quot;&gt;&lt;code&gt;Canvas&lt;/code&gt; CSS system color&lt;/a&gt;
is &lt;code&gt;#121212&lt;/code&gt; in Chrome in dark mode.) I
&lt;a href=&quot;https://webaim.org/resources/contrastchecker/?fcolor=FF0000&amp;amp;bcolor=121212&quot;&gt;checked the contrast&lt;/a&gt;
between CSS &lt;code&gt;#ff0000&lt;/code&gt; (that is, red) and CSS &lt;code&gt;#121212&lt;/code&gt; (that is, black-ish) and
it&#39;s &lt;code&gt;4.68:1&lt;/code&gt;, which for large text passes both WCAG AA and WCAG  AAA.&lt;/p&gt;
&lt;p&gt;Not something really super actionable, other than maybe a heads up to play with
color-preprocessing if the model&#39;s recognition performance is poorer than you
expected.&lt;/p&gt;
&lt;p&gt;Oh, and almost forgot the results of my stress test: on my MacBook Pro 16-inch,
Nov 2024 with an Apple M4 Pro and 48 GB of RAM, the model was able to keep
up with about one complete (but not necessarily correct) prompt response per
second. (Yes, I know that this machine is not what the average user has.)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/canvas-prompt-api.png&quot; alt=&quot;Test case showing the model gets the time wrong in dark mode when a red font is used to paint on the canvas.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can play with the
&lt;a href=&quot;https://tomayac.github.io/blogccasion-demos/canvas-prompt-api/&quot;&gt;demo&lt;/a&gt; embedded
below, or check out the
&lt;a href=&quot;https://github.com/tomayac/blogccasion-demos/tree/main/canvas-prompt-api&quot;&gt;source code&lt;/a&gt;
on GitHub. Toggle between light mode and dark mode and choose red or
&lt;code&gt;CanvasText&lt;/code&gt; as the font color.&lt;/p&gt;
&lt;iframe src=&quot;https://tomayac.github.io/blogccasion-demos/canvas-prompt-api/&quot; allow=&quot;language-model&quot; style=&quot;border: none; width: 100%; height: 600px;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; It&#39;s a lot worse if the canvas background color is pure black
&lt;code&gt;#000000&lt;/code&gt;. I&#39;ve updated the demo to use pure black, and have filed a
&lt;a href=&quot;https://crbug.com/443950694&quot;&gt;Chromium bug&lt;/a&gt;.&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F09%2F16%2Fprompt-api-color-sensitivity%2F&amp;dp=%2F2025%2F09%2F16%2Fprompt-api-color-sensitivity%2F&amp;dt=Prompt%20API%20color%20sensitivity&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2025/09/16/prompt-api-color-sensitivity/&quot;&gt;https://blog.tomayac.com/2025/09/16/prompt-api-color-sensitivity/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>For all that&#39;s holy, can you just leverage the Web, please?</title>
		<link href="https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/"/>
		<updated>2025-09-03T09:59:24Z</updated>
		<id>https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/</id>
		<content type="html">
			&lt;p&gt;When I moved in with my wife Laura in 2005, we lived in a shared apartment in
Barcelona that had an ancient washing machine that was just there already, no
idea who initially bought it. I managed to break the washing machine door&#39;s
closing mechanism some time in 2006, so for a few weeks, whenever we did the
washing, we had to lean a chair against the door so it wouldn&#39;t open. At the
time, we were both students and living on a small budget.&lt;/p&gt;
&lt;p&gt;Eventually, later in the same year, we bought an Electrolux machine that has
accompanied us ever since. First on our move to Hamburg, then there through
three apartments, and finally back to Spain, where we live now in the Catalonian
countryside. Anyway, the washing machine had a motor damage last week, so after
almost 20 years, it was time for a new one. I ordered it online (another
Electrolux, &lt;em&gt;without&lt;/em&gt; Internet nor WiFi), it was delivered swiftly, and I
installed it hopefully correctly.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/electrolux.png&quot; alt=&quot;Our new Electrolux washing machine.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The washing machine came with a voluntary 10 year warranty if you registered it.
The brochure where this offer was announced featured a free telephone number and
a QR code that pointed at the number (in plain text, not making use of the
&lt;code&gt;tel:&lt;/code&gt; protocol). I called the number, and to my &lt;em&gt;absolute surprise&lt;/em&gt; there were
currently more callers than usual. After about 20 minutes, I had an agent on the
phone, but after saying what I wanted, they just hung up on me (or the
connection cut, whatever). Fine, I called again, but now, the call center was
over capacity and they didn&#39;t even let me enter in the wait loop.&lt;/p&gt;
&lt;p&gt;They did offer to send me a link to a chat service on their website via SMS,
though, so I went for that option. The SMS literally pointed me at something
like &lt;code&gt;https://www.&lt;/code&gt; broken up by a space and then &lt;code&gt;example.com/gc/&lt;/code&gt;. When I
clicked the linkified &lt;code&gt;example.com/gc/&lt;/code&gt;, I ended up on a broken site whose
certificate wasn&#39;t trusted. After fixing the link manually and prepending the
&lt;code&gt;https://www.&lt;/code&gt; part, the page didn&#39;t load.&lt;/p&gt;
&lt;p&gt;At this point I was close to giving up, but I had one last card that I wanted to
play: I searched Google for &amp;quot;electrolux warranty register&amp;quot;, and it pointed me at
a site &lt;code&gt;https://www.example.com/mypages/register-a-product/&lt;/code&gt; as the first
result. This looked promising. The &lt;code&gt;mypages&lt;/code&gt; already suggested that this was
gated behind a login, so I created an account, which was painless. (Turns out,
after having an account and being logged in, the chat URL also worked—what an
oversight on their part.) On the page, they had a field where you could enter
the washing machine&#39;s product number from the identification plate on the door
of the washing machine, together with helpful information where to find the
data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/find-pnc-description-electrolux-serialplate.avif&quot; alt=&quot;Annotated Electrolux identification plate.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;But even better, they offered a service where you could just upload a picture of
the identification plate, and some AI on their server then extracted the product
number and let you register the product with two clicks. What a fantastic
experience compared to the crappy (and likely for the operator way more
expensive) call center experience.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/electrolux-plate.jpg&quot; alt=&quot;Electrolux identification plate cell phone photo.&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Why they didn&#39;t just put this URL on the brochure and the QR code is beyond me.
As the title suggests: &lt;strong&gt;For all that&#39;s holy, can you just leverage the Web,
please?&lt;/strong&gt; Don&#39;t make me talk to people! They could still offer to register the
machine by telephone as an alternative, but in 2025, the default for such things
should just be the Web.&lt;/p&gt;
&lt;h2 id=&quot;bonus&quot; tabindex=&quot;-1&quot;&gt;Bonus &lt;a class=&quot;direct-link&quot; href=&quot;https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/#bonus&quot; aria-hidden=&quot;true&quot;&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since I work on &lt;a href=&quot;https://developer.chrome.com/docs/ai/built-in&quot;&gt;built-in AI&lt;/a&gt; as
my day job in the Chrome team at Google, I could not &lt;em&gt;not&lt;/em&gt; notice this &lt;em&gt;&amp;quot;extract
the product number from this identification plate&amp;quot;&lt;/em&gt; use case for client-side AI.
I coded up a quick
&lt;a href=&quot;https://tomayac.github.io/blogccasion-demos/built-in-ai-product-number-ocr/&quot;&gt;demo&lt;/a&gt;
using the &lt;a href=&quot;https://developer.chrome.com/docs/ai/prompt-api&quot;&gt;Prompt API&lt;/a&gt; embedded
below that shows this in action. Here&#39;s a quick walkthrough of the code:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a session with the &lt;code&gt;LanguageModel&lt;/code&gt;, informing the user of download
progress if the model needs to be downloaded, and telling the model about the
to-be-expected inputs (English texts and images) and outputs (English texts).
In the system prompt, I tell the model what its overall task is (identify
product numbers from photos of identification plates).&lt;/li&gt;
&lt;li&gt;Prompt the model using the &lt;code&gt;promptStreaming()&lt;/code&gt; method with a multimodal
prompt, one textual and one image. The Prompt API supports
&lt;a href=&quot;https://developer.chrome.com/docs/ai/structured-output-for-prompt-api?hl=en&quot;&gt;structured output&lt;/a&gt;
in the form of a JSON Schema or regular expression. Product numbers have nine
digits, so I pass the regular expression &lt;code&gt;/&#92;d{9}/&lt;/code&gt; as the
&lt;code&gt;responseConstraint&lt;/code&gt; option.&lt;/li&gt;
&lt;li&gt;Iterate over the chunks of the response. Since I&#39;m just expecting nine
digits, this is probably a bit overkill, but, hey…&lt;/li&gt;
&lt;li&gt;(Not shown) On the server, verify that the recognized product number actually
exist. Companies typically have some sort of verification rules like
checksums, or washing machine product numbers always start with &lt;code&gt;91&lt;/code&gt; or
something. If you know those rules, you can of course make them part of the
&lt;code&gt;responseConstraint&lt;/code&gt;, but you always need to verify untrusted user input
(which the output of an LLM counts as) on the server.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; LanguageModel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;monitor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    m&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;downloadprogress&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Downloaded &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;loaded &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;%.&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;expectedInputs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;en&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;expectedOutputs&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;languages&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;en&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;initialPrompts&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;system&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&#39;Your task is to identify product numbers from photos of identification plates.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; stream &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;promptStreaming&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;user&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
          &lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&#39;Extract the product number from this identification plate. It has nine digits and appears after the text &quot;Prod.No.&quot;.&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;image&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; image &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;responseConstraint&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;&#92;d{9}&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; chunk &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; stream&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;chunk&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;iframe src=&quot;https://tomayac.github.io/blogccasion-demos/built-in-ai-product-number-ocr/&quot; allow=&quot;language-model&quot; style=&quot;border: none; width: 100%; height: 600px;&quot;&gt;&lt;/iframe&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F09%2F03%2Ffor-all-thats-holy-can-you-just-leverage-the-web-please%2F&amp;dp=%2F2025%2F09%2F03%2Ffor-all-thats-holy-can-you-just-leverage-the-web-please%2F&amp;dt=For%20all%20that&#39;s%20holy%2C%20can%20you%20just%20leverage%20the%20Web%2C%20please%3F&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/&quot;&gt;https://blog.tomayac.com/2025/09/03/for-all-thats-holy-can-you-just-leverage-the-web-please/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
	
	
	<entry>
		<title>What a diff&#39;rence a semicolon makes</title>
		<link href="https://blog.tomayac.com/2025/07/26/what-a-difference-a-semicolon-makes/"/>
		<updated>2025-07-26T00:32:20Z</updated>
		<id>https://blog.tomayac.com/2025/07/26/what-a-difference-a-semicolon-makes/</id>
		<content type="html">
			&lt;p&gt;The other day, I was hit by a baffling
&lt;code&gt;TypeError: console.log(...) is not a function&lt;/code&gt;. Like, WTF 🤔? Turns out, I was
sloppily adding a quick &lt;code&gt;console.log(&#39;here&#39;)&lt;/code&gt; statement for debugging purposes
(as one does 🙈), which happened to be right before an
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/IIFE&quot;&gt;IIFE&lt;/a&gt;. I didn&#39;t put a
&lt;code&gt;;&lt;/code&gt;, as it was a throwaway statement I&#39;d remove after finding the bug, but turns
out that&#39;s the issue. StackOverflow contributor
&lt;a href=&quot;https://stackoverflow.com/users/4642212/sebastian-simon&quot;&gt;Sebastian Simon&lt;/a&gt; had
the &lt;a href=&quot;https://stackoverflow.com/a/31013390&quot;&gt;explanation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&#39;s trying to pass &lt;code&gt;function(){}&lt;/code&gt; as an argument to the return value of
&lt;code&gt;console.log()&lt;/code&gt; which itself is not a function but actually undefined (check
&lt;code&gt;typeof console.log();&lt;/code&gt;). This is because JavaScript interprets this as
&lt;code&gt;console.log()(function(){})&lt;/code&gt;. &lt;code&gt;console.log&lt;/code&gt; however is a function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Minimal repro:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://fedi.jaenis.ch/@andre&quot;&gt;Andre&lt;/a&gt; on Mastodon
&lt;a href=&quot;https://fedi.jaenis.ch/@andre/statuses/01K10C5PY7JBYVXYG8T6J4SP4B&quot;&gt;reminded me&lt;/a&gt;
of &lt;a href=&quot;https://front-end.social/@chriscoyier&quot;&gt;Chris Coyier&lt;/a&gt;&#39;s excellent
&lt;a href=&quot;https://css-tricks.com/web-development-merit-badges/&quot;&gt;Web Development Merit Badges&lt;/a&gt;,
so I&#39;m now proudly wearing mine: &lt;em&gt;&amp;quot;Debugged something for over one hour where
the fix was literally one character&amp;quot;&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.tomayac.com/images/merit-badge-onechar.svg&quot; alt=&quot;Debugged something for over one hour where the fix was literally one character&quot; /&gt;&lt;/p&gt;

			&lt;p&gt;
				&lt;img alt=&quot;Thomas Steiner&quot; width=&quot;32&quot; height=&quot;32&quot; src=&quot;https://blog.tomayac.com/feed.php?dl=https%3A%2F%2Fblog.tomayac.com%2F2025%2F07%2F26%2Fwhat-a-difference-a-semicolon-makes%2F&amp;dp=%2F2025%2F07%2F26%2Fwhat-a-difference-a-semicolon-makes%2F&amp;dt=What%20a%20diff&#39;rence%20a%20semicolon%20makes&quot; alt=&quot;&quot;&gt;
				&lt;br/&gt;This post appeared first on &lt;a href=&quot;https://blog.tomayac.com/2025/07/26/what-a-difference-a-semicolon-makes/&quot;&gt;https://blog.tomayac.com/2025/07/26/what-a-difference-a-semicolon-makes/&lt;/a&gt;.
			&lt;/p&gt;
		</content>
	</entry>
</feed>
