<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roberto Vázquez González</title>
    <description>The latest articles on DEV Community by Roberto Vázquez González (@robertovg).</description>
    <link>https://dev.to/robertovg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F340951%2Fb84f70a1-e17f-496c-a770-df568da19aea.jpeg</url>
      <title>DEV Community: Roberto Vázquez González</title>
      <link>https://dev.to/robertovg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robertovg"/>
    <language>en</language>
    <item>
      <title>Verifiable Bookmarklets</title>
      <dc:creator>Roberto Vázquez González</dc:creator>
      <pubDate>Mon, 27 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/robertovg/verifiable-bookmarklets-ae9</link>
      <guid>https://dev.to/robertovg/verifiable-bookmarklets-ae9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8d0hhhdl0wcztt1q5xr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx8d0hhhdl0wcztt1q5xr.png" alt="Browser dragging bookmarklet to the bookmark" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just added a small new tool to the site: &lt;a href="https://robertovg.com/projects/verifiable-bookmarklets/" rel="noopener noreferrer"&gt;Verifiable Bookmarklets&lt;/a&gt;. Every bookmarklet it generates carries a SHA-256 fingerprint, so anyone can check that the &lt;code&gt;javascript:&lt;/code&gt; link they received was really built from the source code they were shown.&lt;/p&gt;

&lt;p&gt;It is a lightweight reproducible-builds mindset applied to bookmarklets. Nothing more than that.&lt;/p&gt;

&lt;p&gt;The question it answers is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can this bookmarklet be proven to match this source code?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is all. It does not make bookmarklets secure. It does not sandbox them. It does not replace browser extensions or browser stores. A verified bookmarklet can still be bad code, dangerous code or code you should not run. But at least you can know whether the bookmarklet you received is exactly the bookmarklet that was built from the source being shown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bookmarklets?
&lt;/h2&gt;

&lt;p&gt;Bookmarklets are old, simple and still useful. You drag a link to the bookmarks bar, click it later and the browser runs a small piece of JavaScript in the current page. No install flow, no store review, no account, no backend.&lt;/p&gt;

&lt;p&gt;I still use them every day at work. These are real ones I have written over the years to make repetitive tasks easier, all of them from a pre-AI world, but the same principles apply:&lt;/p&gt;

&lt;h3&gt;
  
  
  🐙 GitHub PR Feedback Extractor v1.0.0
&lt;/h3&gt;

&lt;p&gt;The newest one. Run it on a PR and it opens a dialog listing every comment grouped by author. I tick the ones I care about and it copies a Markdown extraction I can paste straight into an AI agent to iterate on and address.&lt;/p&gt;

&lt;p&gt;A GitHub skill with access to the GitHub CLI could do the same, but when I already have the comments in front of me, clicking a bookmarklet is just faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗📋 Copy page link v1.0.3
&lt;/h3&gt;

&lt;p&gt;Generates a Markdown link to the current page and copies it to the clipboard. If there is text selected it becomes the link text, otherwise it falls back to the page title.&lt;/p&gt;

&lt;h3&gt;
  
  
  📤 Full Site To Markdown Extractor v1.0.3
&lt;/h3&gt;

&lt;p&gt;Extracts the content of a page as Markdown so I can feed it to an AI agent. I will expand on this one in a following post.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕳 🧩 🦑 📋 Show / Hide the Content Extractor v1.0.9
&lt;/h3&gt;

&lt;p&gt;Shows an input on the page where I can type a CSS selector. It highlights the matching elements and copies their content to the clipboard.&lt;/p&gt;

&lt;p&gt;Most of my others are too specific to my daily work to be worth publishing. The point is the shape of the list: tiny, boring, single-purpose automations that pull an identifier out of a page, reformat something into Markdown, or save me four clicks in an internal tool. An extension would be overkill for every single one of them.&lt;/p&gt;

&lt;p&gt;Notice the version numbers too. These things get edited, regenerated and re-dragged constantly, which is exactly where provenance starts to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing them never worked well
&lt;/h2&gt;

&lt;p&gt;There is no standard way to share a bookmarklet. Drag one to your desktop and you get a &lt;code&gt;.inetloc&lt;/code&gt; file on macOS, a &lt;code&gt;.url&lt;/code&gt; file on Windows or a &lt;code&gt;.desktop&lt;/code&gt; file on Linux. They all contain the &lt;code&gt;javascript:&lt;/code&gt; URL, but they are not human-readable and they are not nice to share.&lt;/p&gt;

&lt;p&gt;For years I generated bookmarklets with &lt;a href="https://caiorss.github.io/bookmarklet-maker/" rel="noopener noreferrer"&gt;Bookmarklet Maker&lt;/a&gt;, which takes normal JavaScript and gives you back a bookmarklet ready to drag into the browser. That solved generation well enough that I never looked for anything else. I shared them with colleagues, they shared them with me, and I treated them as informal web scripts without any more pretension than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that changed recently
&lt;/h2&gt;

&lt;p&gt;Because of LLMs, the generation step has become trivial. Ask for "a bookmarklet that does X" and you get working code back in seconds. That is genuinely useful and I do it often. It also means a lot more &lt;code&gt;javascript:&lt;/code&gt; URLs are being generated, pasted into Slack and shared between people who never saw the source.&lt;/p&gt;

&lt;p&gt;So more mates share bookmarklets with me, and I share more with them. Generation is no longer the bottleneck.&lt;/p&gt;

&lt;p&gt;Which is when it hit me: &lt;strong&gt;how do I know that the bookmarklet I received is exactly the one that was built from the source I was shown?&lt;/strong&gt; And how can I be sure the bookmarklet I am about to share is exactly the one built from the source I am showing?&lt;/p&gt;

&lt;p&gt;Whether that source was written by me, by a colleague or by a model does not really change the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this bookmarklet exactly what this source produces?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Share an opaque &lt;code&gt;javascript:&lt;/code&gt; URL&lt;/li&gt;
&lt;li&gt;Trust the author&lt;/li&gt;
&lt;li&gt;Impossible to compare&lt;/li&gt;
&lt;li&gt;"Looks fine"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Share a source URL&lt;/li&gt;
&lt;li&gt;Verify the build&lt;/li&gt;
&lt;li&gt;Deterministic SHA-256&lt;/li&gt;
&lt;li&gt;"Matches source"&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Each generated bookmarklet contains a small metadata comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*bm:v1;sha=&amp;lt;sha256&amp;gt;;src=&amp;lt;optional-url&amp;gt;*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool builds the payload, hashes it with SHA-256 and embeds that comment. The hash does not include the metadata itself, which avoids the circular problem of hashing something that contains its own hash.&lt;/p&gt;

&lt;p&gt;The pipeline is deliberately short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;normalize source (normalize newlines, trim)
      ↓
build payload
      ↓
SHA-256(payload)
      ↓
embed metadata
      ↓
share
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool can already:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate a bookmarklet from pasted JavaScript source&lt;/li&gt;
&lt;li&gt;generate a bookmarklet from a raw JavaScript URL&lt;/li&gt;
&lt;li&gt;create a draggable bookmarklet link&lt;/li&gt;
&lt;li&gt;inspect a pasted or dropped bookmarklet&lt;/li&gt;
&lt;li&gt;show decoded JavaScript and metadata&lt;/li&gt;
&lt;li&gt;verify a bookmarklet against the current source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs in the browser. No backend, no login, no publishing service.&lt;/p&gt;

&lt;p&gt;It also generates a shareable link to the tool with the source URL as a parameter, so anyone can review the source and generate the bookmarklet themselves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://robertovg.com/projects/verifiable-bookmarklets/?src=&lt;url-encoded-source" rel="noopener noreferrer"&gt;https://robertovg.com/projects/verifiable-bookmarklets/?src=&amp;amp;lt;url-encoded-source&lt;/a&gt;&amp;gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two workflows
&lt;/h2&gt;

&lt;p&gt;That source URL parameter turned out to be the better way to share a bookmarklet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authoring.&lt;/strong&gt; Publish the source, for example as a GitHub Gist, and link to Verifiable Bookmarklets with that URL as a parameter. The tool fetches the source, generates the bookmarklet locally and shows the fingerprint. Every reader reviews plain JavaScript and builds the executable in their own browser instead of trusting a long opaque link. Pinning a specific Gist revision rather than the mutable latest URL makes the reference stronger, because the source itself cannot change under you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification.&lt;/strong&gt; When someone shares the bookmarklet itself — "I found this in Slack, is it still the original?" — the embedded metadata is what helps. Inspect it, read the &lt;code&gt;src&lt;/code&gt; and &lt;code&gt;sha&lt;/code&gt;, fetch the source, regenerate and compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  What verification means
&lt;/h2&gt;

&lt;p&gt;Verification means:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This bookmarklet matches this source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does not mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this bookmarklet is safe&lt;/li&gt;
&lt;li&gt;this bookmarklet is trustworthy&lt;/li&gt;
&lt;li&gt;this source is the original source&lt;/li&gt;
&lt;li&gt;this source URL will always return the same code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is about provenance, not security. I think that distinction matters. Small tools become more useful when they make narrow claims and keep those claims honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three functions that carry the idea
&lt;/h2&gt;

&lt;p&gt;Generating the payload and the fingerprint &lt;code&gt;createPayload&lt;/code&gt;, &lt;code&gt;createMetadata&lt;/code&gt; and &lt;code&gt;verify&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizeSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\r\n?&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;minifySource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;normalizeSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encodeBookmarkletValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/'/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%27&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/"/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%22&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encodeBookmarkletBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`javascript:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;encodeBookmarkletBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`(()=&amp;gt;{&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;minifySource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;})()`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`/*bm:v1;sha=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;;src=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeBookmarkletValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;*/`&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/*bm:v1;sha=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*/`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And verifying, which rebuilds the payload from the decoded bookmarklet, hashes it and compares it against the embedded &lt;code&gt;sha&lt;/code&gt;. It never executes the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookmarklet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hasScheme&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeBookmarklet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bookmarklet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payloadBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;removeMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hasScheme&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;encodeBookmarkletBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payloadBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payloadBody&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sha&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;createPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deterministic step stays conservative on purpose: normalize newlines, trim the source, hash the generated payload. It just turns JavaScript you could run in your console into a bookmarklet you can check.&lt;/p&gt;

&lt;p&gt;The rest of the implementation lives in &lt;a href="https://github.com/robertovg/robertovgGatsbyjs/blob/main/src/pages/projects/verifiable-bookmarklets.js#L417" rel="noopener noreferrer"&gt;this site's GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://robertovg.com/projects/verifiable-bookmarklets/" rel="noopener noreferrer"&gt;Open Verifiable Bookmarklets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of sending someone an opaque &lt;code&gt;javascript:&lt;/code&gt; URL, I can now send them a link to the source and let them generate the executable themselves, like this &lt;a href="https://robertovg.com/projects/verifiable-bookmarklets/?src=https%3A%2F%2Fgist.githubusercontent.com%2Frobertovg%2Fc6ed0ef05a3d1d4c958f551c2abb54c8%2Fraw%2F0de7d7616583305f1aad920910e922e86c0e266d%2FFull%252520Site%252520To%252520Markdown%252520Extractor%252520-%252520bookmarklet.js" rel="noopener noreferrer"&gt;📤 Full Site To Markdown Extractor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AI made generating bookmarklets trivial. I wanted to simplify the sharing process and give some transparency to the provenance of the code. This tool does that, and I hope it is useful to others too.&lt;/p&gt;

</description>
      <category>bookmarklets</category>
      <category>javascript</category>
      <category>tools</category>
      <category>provenance</category>
    </item>
    <item>
      <title>My /uses page, six years later</title>
      <dc:creator>Roberto Vázquez González</dc:creator>
      <pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/robertovg/my-uses-page-six-years-later-1dn3</link>
      <guid>https://dev.to/robertovg/my-uses-page-six-years-later-1dn3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6pz14gttsiz2q7vkmgv2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6pz14gttsiz2q7vkmgv2.png" alt="uses.tech banner 2026" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I just updated my &lt;a href="https://robertovg.com/uses/" rel="noopener noreferrer"&gt;/uses page&lt;/a&gt; for the first time since... well, since a version of me that wrote JavaScript without types, deployed with create-react-app, and had never talked to an AI in a terminal.&lt;/p&gt;

&lt;p&gt;Instead of quietly rewriting it and pretending nothing happened, I turned the page into a small timeline: what I use now on top, and the old version preserved below as historical data. I like these pages more when they're honest about time passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually changed
&lt;/h2&gt;

&lt;p&gt;Looking at the diff between 2019-me and 2026-me, a few things stand out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript won.&lt;/strong&gt; I don't write plain JavaScript anymore, anywhere. TS on the frontend, TS on the backend with Nest.js. Python and Go joined the toolbox too, and Java quietly became an old friend I never call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tooling turned over almost completely.&lt;/strong&gt; Alfred became Raycast. My terminal became Warp. create-react-app became Vite. Gatsby and Next.js are still around in projects I maintain, but Astro is where my new sites go. And most of my new projects now live on Cloudflare (Pages, Workers, KV) instead of scattered across half the internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI agents are the biggest change by far.&lt;/strong&gt; In 2019, "Editors" was a section about VSCode plugins. Now it's "Editors / AI Agents", because Claude Code, Codex, Copilot and friends are part of my daily workflow, both at work and on side projects. I still care about the same thing I cared about six years ago, though: the best tools are the ones that make life easier without the effort behind them being visible. That hasn't changed, only the tools have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hardware section got simpler.&lt;/strong&gt; Same setup at home and at &lt;a href="https://corkerspace.com" rel="noopener noreferrer"&gt;Corker&lt;/a&gt; (the coworking space I co-founded): MacBook Pro 16" behind an external monitor, external keyboard and mouse, so moving between the two feels like nothing. The AKG K240 + Aune DAC combo from the old list is still going strong. Some things are worth keeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  What all of this is for
&lt;/h2&gt;

&lt;p&gt;A /uses page can look like a list of shiny things, but for me it's the workshop, not the work. Most of this stack is currently pointed at being productive. AI is here to stay, and I genuinely like how it simplifies code creation, both at work and outside of it. But I also like to keep creating things from scratch, no AI assistance, just to keep my coding brain sharp and in good shape, up to date with how things actually work under the hood.&lt;/p&gt;

&lt;p&gt;If something on the list catches your eye and you want to know more, reach out. And if you have a /uses page of your own, I'd genuinely love to see the diff between your past and present self.&lt;/p&gt;

</description>
      <category>website</category>
      <category>uses</category>
      <category>ai</category>
      <category>tools</category>
    </item>
    <item>
      <title>Rebooting this website</title>
      <dc:creator>Roberto Vázquez González</dc:creator>
      <pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/robertovg/rebooting-this-website-388b</link>
      <guid>https://dev.to/robertovg/rebooting-this-website-388b</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15amfvh7y8ehbmqbmqwb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F15amfvh7y8ehbmqbmqwb.png" alt="Shell session rebooting my site" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's been a while.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://robertovg.com/blog/here-my-uses-page/" rel="noopener noreferrer"&gt;The last article&lt;/a&gt; I published here was back in 2020, when I wrote about my &lt;a href="https://robertovg.com/uses/" rel="noopener noreferrer"&gt;/uses&lt;/a&gt; page. Looking back, it's hard to believe how much has changed since then, both in my life and in the tools I use every day. Updating that page is definitely overdue, but I'll save that for another day.&lt;/p&gt;

&lt;p&gt;The world went through the COVID-19 pandemic and lockdowns, life moved on, and over the years this website slowly stopped being a priority.&lt;/p&gt;

&lt;p&gt;Not because I stopped building things, quite the opposite.&lt;/p&gt;

&lt;p&gt;Since then, I've been working at EverQuote, where I've had the opportunity to work on products at a much larger scale than ever before. I also married my life partner, Nuria, and not long afterwards our family started to grow. First came Berta, and two years later Elio, by far the biggest and best change in our lives.&lt;/p&gt;

&lt;p&gt;Like many people, I simply had different priorities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplifying things
&lt;/h2&gt;

&lt;p&gt;One thing I did want to improve was where all my personal projects live.&lt;/p&gt;

&lt;p&gt;Over the years I ended up hosting different sites across AWS, Vercel, Netlify, Google Cloud and a few other places. It was fun, and a great excuse to learn and compare different platforms, but it also meant jumping between providers every time I wanted to touch one of my projects.&lt;/p&gt;

&lt;p&gt;Lately I've been moving everything to Cloudflare.&lt;/p&gt;

&lt;p&gt;For my personal projects, it hits a really nice balance between simplicity, performance and cost. Having hosting, deployments and domain management in the same place makes maintaining everything much easier.&lt;/p&gt;

&lt;p&gt;Migrating everything there also gave me the perfect excuse to revisit this website, refresh the content and make it better reflect who I am today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking forward
&lt;/h2&gt;

&lt;p&gt;This isn't a "big relaunch". Just a small reboot.&lt;/p&gt;

&lt;p&gt;I have a personal project I'm very excited about and that I hope to start shipping soon.&lt;/p&gt;

&lt;p&gt;Its name is &lt;strong&gt;SongForm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My hope is that this website becomes a place where I occasionally document that journey, share things I learn along the way, and publish the odd article when I have something worth sharing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why keep writing?
&lt;/h2&gt;

&lt;p&gt;I did wonder whether maintaining a personal website still made sense in a world increasingly filled with AI-generated content, or what better said "AI slop."&lt;/p&gt;

&lt;p&gt;The more I thought about it, the more I realised that's exactly why it does.&lt;/p&gt;

&lt;p&gt;I use AI every day, both personally and professionally. I think it's one of the most useful &lt;strong&gt;TOOLS&lt;/strong&gt; I've ever had. It helps me build faster, execute ideas more efficiently, research unfamiliar topics and augment my knowledge.&lt;/p&gt;

&lt;p&gt;That said, I've found it's most valuable when I already understand the subject well enough to review its output critically, separate facts from hallucinations and apply my own experience.&lt;/p&gt;

&lt;p&gt;Personal websites were never about producing as much content as possible. They're about documenting a journey, sharing lessons learned and occasionally building something that someone else might find useful.&lt;/p&gt;

&lt;p&gt;AI can help me write. It can help me code. But it can't accumulate my experiences for me.&lt;/p&gt;

&lt;p&gt;So here's to rebooting this little corner of the internet.&lt;/p&gt;

&lt;p&gt;Hopefully this is the start of writing here a little more often.&lt;/p&gt;

</description>
      <category>website</category>
      <category>cloudflare</category>
      <category>family</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
