{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Ricardo Mendes",
  "home_page_url": "https://rmendes.net/",
  "feed_url": "https://rmendes.net/feed.json",
  "hubs": [
    {
      "type": "WebSub",
      "url": "https://websubhub.com/hub"
    }
  ],
  "description": "This site is my personal hub for long-form writing, curated bookmarks, and open-web experiments — where ideas about tech, autonomy, democracy, and digital culture meet.
Since February 2026, this site is also my personal ActivityPub instance, every posts you see on this blog can be fetched from the fediverse.",
  "language": "en",
  "authors": [
    {
      "name": "Ricardo Mendes",
      "url": "https://rmendes.net/"
    }
  ],
  "_textcasting": {
    "version": "1.0",
    "about": "https://textcasting.org/"
  },
  "items": [
    {
      "id": "https://rmendes.net/articles/2026/06/06/the-brexit-bus-was-only/",
      "url": "https://rmendes.net/articles/2026/06/06/the-brexit-bus-was-only/",
      "title": "The Brexit Bus Was Only the Beginning",
      "content_html": "<p>When historians write about the decline of democratic institutions in the early twenty-first century, they may not focus on artificial intelligence, deepfakes or social media algorithms.</p>\n<p>They may focus on a bus.</p>\n<p>A red bus promised that leaving the European Union would free up £350 million a week for the NHS. The claim was repeatedly challenged, repeatedly debunked, and yet it worked. Not because it was true, but because it was emotionally satisfying.</p>\n<p>Ten years later, the argument is no longer about whether Brexit delivered what was promised. The economic consequences are still debated, but something arguably more important happened: a lesson was learned.</p>\n<p>The lesson was that political narratives no longer needed to be true.</p>\n<p>They only needed to spread.</p>\n<p>The Brexit campaign was not the first political movement to use misinformation. Propaganda is as old as politics itself. What changed was the information environment. Social media platforms transformed the economics of persuasion.</p>\n<p>For centuries, publishing information carried costs. Newspapers had editors. Broadcasters had regulations. Journalists had professional standards. False information could spread, but it faced friction.</p>\n<p>The internet removed much of that friction.</p>\n<p>Social media platforms then discovered something even more consequential: outrage, fear, tribal identity and moral panic generated engagement. Engagement generated advertising revenue. The incentives aligned perfectly.</p>\n<p>The result was not merely the spread of misinformation.</p>\n<p>The result was the industrialization of misinformation.</p>\n<p>Today, we often discuss disinformation as though it were an unfortunate side effect of technology. Yet it is increasingly difficult to view it that way. The largest platforms on Earth possess vast resources, employ thousands of researchers and have unparalleled visibility into how information flows through society.</p>\n<p>They know which content drives engagement.</p>\n<p>They know which narratives spread fastest.</p>\n<p>They know which emotions keep users scrolling.</p>\n<p>And yet the underlying business model remains largely unchanged.</p>\n<p>Brexit was not caused by Facebook, Google, Twitter or YouTube.</p>\n<p>But Brexit revealed a new political reality: a sufficiently compelling narrative could outperform evidence, expertise and fact-checking.</p>\n<p>Since then, the same dynamics have appeared repeatedly across the democratic world.</p>\n<p>The issue is no longer any single election, referendum or political movement.</p>\n<p>The issue is that democratic societies increasingly depend upon information ecosystems whose incentives are fundamentally misaligned with democratic health.</p>\n<p>A healthy democracy requires informed citizens.</p>\n<p>A platform requires engaged users.</p>\n<p>These are not necessarily the same thing.</p>\n<p>One day there may be public inquiries into the role social media corporations played in accelerating polarization, amplifying disinformation and weakening public trust in institutions.</p>\n<p>Not because these companies intended to damage democracy.</p>\n<p>But because they built systems optimized for engagement while treating the resulting societal consequences as someone else’s problem.</p>\n<p>The question future generations may ask is not whether we knew the damage was occurring.</p>\n<p>The question may be why we allowed it to continue for so long.</p>\n<blockquote>\n<p>Britain is a swamp of lies and disinformation – and we got here on the Brexit bus by <a href=\"https://www.theguardian.com/commentisfree/2026/jun/05/britain-lies-disinformation-brexit-bus-economy-vote\">Jonathan Freedland</a></p>\n</blockquote>\n",
      "content_text": "When historians write about the decline of democratic institutions in the early twenty-first century, they may not focus on artificial intelligence, deepfakes or social media algorithms. They may focus on a bus. A red bus promised that leaving the European Union would free up £350 million a week for the NHS. The claim was repeatedly challenged, repeatedly debunked, and yet it worked. Not because it was true, but because it was emotionally satisfying. Ten years later, the argument is no longer about whether Brexit delivered what was promised. The economic consequences are still debated, but something arguably more important happened: a lesson was learned. The lesson was that political narratives no longer needed to be true. They only needed to spread. The Brexit campaign was not the first political movement to use misinformation. Propaganda is as old as politics itself. What changed was the information environment. Social media platforms transformed the economics of persuasion. For centuries, publishing information carried costs. Newspapers had editors. Broadcasters had regulations. Journalists had professional standards. False information could spread, but it faced friction. The internet removed much of that friction. Social media platforms then discovered something even more consequential: outrage, fear, tribal identity and moral panic generated engagement. Engagement generated advertising revenue. The incentives aligned perfectly. The result was not merely the spread of misinformation. The result was the industrialization of misinformation. Today, we often discuss disinformation as though it were an unfortunate side effect of technology. Yet it is increasingly difficult to view it that way. The largest platforms on Earth possess vast resources, employ thousands of researchers and have unparalleled visibility into how information flows through society. They know which content drives engagement. They know which narratives spread fastest. They know which emotions keep users scrolling. And yet the underlying business model remains largely unchanged. Brexit was not caused by Facebook, Google, Twitter or YouTube. But Brexit revealed a new political reality: a sufficiently compelling narrative could outperform evidence, expertise and fact-checking. Since then, the same dynamics have appeared repeatedly across the democratic world. The issue is no longer any single election, referendum or political movement. The issue is that democratic societies increasingly depend upon information ecosystems whose incentives are fundamentally misaligned with democratic health. A healthy democracy requires informed citizens. A platform requires engaged users. These are not necessarily the same thing. One day there may be public inquiries into the role social media corporations played in accelerating polarization, amplifying disinformation and weakening public trust in institutions. Not because these companies intended to damage democracy. But because they built systems optimized for engagement while treating the resulting societal consequences as someone else’s problem. The question future generations may ask is not whether we knew the damage was occurring. The question may be why we allowed it to continue for so long. Britain is a swamp of lies and disinformation – and we got here on the Brexit bus by Jonathan Freedland",
      "date_published": "2026-06-06T10:15:32Z",
      "date_modified": "2026-06-06T10:18:09Z"
    },
    {
      "id": "https://rmendes.net/replies/2026/06/01/adbe4/",
      "url": "https://rmendes.net/replies/2026/06/01/adbe4/",
      "title": null,
      "content_html": "<p>The paradox of a country governed by lawyers is that an obsession with procedure can become a weakness when confronted by authoritarianism. Whether the threat comes from corporations, the state, or an unofficial dictatorship, legalism alone is a poor defense. The United States, for all its self-congratulatory mythology about having “the best constitution in the world,” increasingly looks like a case study in democratic erosion. At times, even countries like Hungary or Turkey have shown greater institutional resilience.</p>\n",
      "content_text": "The paradox of a country governed by lawyers is that an obsession with procedure can become a weakness when confronted by authoritarianism. Whether the threat comes from corporations, the state, or an unofficial dictatorship, legalism alone is a poor defense. The United States, for all its self-congratulatory mythology about having “the best constitution in the world,” increasingly looks like a case study in democratic erosion. At times, even countries like Hungary or Turkey have shown greater institutional resilience.",
      "date_published": "2026-06-01T16:12:58Z",
      "date_modified": "2026-06-01T16:13:54Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/31/e9a23/",
      "url": "https://rmendes.net/notes/2026/05/31/e9a23/",
      "title": null,
      "content_html": "<p>Holy crap 😱</p>\n<p>Persistent Orbital Junkyard that’s what <a href=\"https://leolabs.space/\">it</a> <a href=\"https://platform.leolabs.space/visualization\">is</a></p>\n",
      "content_text": "Holy crap 😱 Persistent Orbital Junkyard that’s what it is",
      "date_published": "2026-05-31T16:13:37Z",
      "date_modified": "2026-05-31T16:14:43Z"
    },
    {
      "id": "https://rmendes.net/bookmarks/2026/05/31/reading-how-to-destroy-a/",
      "url": "https://rmendes.net/bookmarks/2026/05/31/reading-how-to-destroy-a/",
      "title": "Reading : how to destroy a literary reputation in one move?",
      "content_html": "<p>Inspired by Dune’s Litany against Fear :</p>\n<p>I must not fear AI.</p>\n<p>Fear is the reputation-killer.</p>\n<p>Fear of being left behind drives institutions to replace judgment with automation.\nFear of losing market share drives executives to trade trust for efficiency.\nFear of missing the future drives them to abandon the very people who made them worth reading.</p>\n<p>I must not worship AI.</p>\n<p>AI is a tool, not a conscience.\nIt can generate words, but not wisdom.\nIt can mimic expertise, but not earn it.\nIt can produce content, but not trust.</p>\n<p>I will permit AI to pass through my workflow and through my tools.</p>\n<p>I will use it, but I will not surrender my judgment to it.</p>\n<p>And when the hype has passed and the dashboards are forgotten, I will turn the inner eye to see what remains.</p>\n<p>Words are abundant.\nContent is abundant.\nInformation is abundant.</p>\n<p>Trust is scarce.</p>\n<p>If trust remains, the institution survives.</p>\n<p>If trust is gone, no amount of generated content can bring it back.</p>\n<p>Only trust will remain.</p>\n",
      "content_text": "Inspired by Dune’s Litany against Fear : I must not fear AI. Fear is the reputation-killer. Fear of being left behind drives institutions to replace judgment with automation. Fear of losing market share drives executives to trade trust for efficiency. Fear of missing the future drives them to abandon the very people who made them worth reading. I must not worship AI. AI is a tool, not a conscience. It can generate words, but not wisdom. It can mimic expertise, but not earn it. It can produce content, but not trust. I will permit AI to pass through my workflow and through my tools. I will use it, but I will not surrender my judgment to it. And when the hype has passed and the dashboards are forgotten, I will turn the inner eye to see what remains. Words are abundant. Content is abundant. Information is abundant. Trust is scarce. If trust remains, the institution survives. If trust is gone, no amount of generated content can bring it back. Only trust will remain.",
      "date_published": "2026-05-31T11:51:44Z",
      "date_modified": "2026-05-31T11:56:33Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/30/66d97/",
      "url": "https://rmendes.net/notes/2026/05/30/66d97/",
      "title": null,
      "content_html": "<p>En réponse à <a href=\"https://www.facebook.com/share/18UWa5nVb9/\">ceci</a></p>\n<p>Deuxième étape pour la France… Donner la même importance et la même visibilité à toutes les victimes plutôt que de favoriser les victimes de chanteurs, d’auteurs, de personnalités connues.\nLa Justice française dort et ne se réveille que quand ses stars sont touchées.</p>\n<p><a href=\"https://chardonsbleus.org/\">https://chardonsbleus.org</a></p>\n<p>Faut-il être victime d’une personnalité connue ou être d’une famille importante pour avoir le droit à la justice?</p>\n",
      "content_text": "En réponse à ceci Deuxième étape pour la France… Donner la même importance et la même visibilité à toutes les victimes plutôt que de favoriser les victimes de chanteurs, d’auteurs, de personnalités connues. La Justice française dort et ne se réveille que quand ses stars sont touchées. https://chardonsbleus.org Faut-il être victime d’une personnalité connue ou être d’une famille importante pour avoir le droit à la justice?",
      "date_published": "2026-05-30T10:18:43Z",
      "date_modified": "2026-05-30T10:19:25Z"
    },
    {
      "id": "https://rmendes.net/bookmarks/2026/05/30/quand-putin-investit-dans-les/",
      "url": "https://rmendes.net/bookmarks/2026/05/30/quand-putin-investit-dans-les/",
      "title": "Quand Putin investit dans les idiots utiles pour servir ses intérêts",
      "content_html": "<p>« les changements de pouvoir promis par l’administration américaine n’auront pas lieu en Russie et en Biélorussie, mais en Europe, où elle s’appuie sur les forces patriotiques de l’extrême droite, prêtes à prendre les commandes ».</p>\n",
      "content_text": "« les changements de pouvoir promis par l’administration américaine n’auront pas lieu en Russie et en Biélorussie, mais en Europe, où elle s’appuie sur les forces patriotiques de l’extrême droite, prêtes à prendre les commandes ».",
      "date_published": "2026-05-30T09:32:32Z",
      "date_modified": "2026-05-30T09:35:45Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/21/05d93/",
      "url": "https://rmendes.net/notes/2026/05/21/05d93/",
      "title": null,
      "content_html": "<p><a href=\"https://rmdes.github.io/plume/\">Plume</a>\nPost to your IndieWeb blog from any page — toolbar composer or right-click capture. Cross-browser, multi-account, no telemetry.</p>\n<p>You need a micropub compatible blog engine to use this extension <a href=\"https://indieweb.org/Micropub/Servers\">List</a></p>\n",
      "content_text": "Plume Post to your IndieWeb blog from any page — toolbar composer or right-click capture. Cross-browser, multi-account, no telemetry. You need a micropub compatible blog engine to use this extension List",
      "date_published": "2026-05-21T15:31:47Z",
      "date_modified": "2026-05-21T15:35:15Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/20/bbf3e/",
      "url": "https://rmendes.net/notes/2026/05/20/bbf3e/",
      "title": null,
      "content_html": "<p>Chaque fois qu’éclate une affaire de viol en France, j’ai la même réflexion : et si c’était un viol d’un inconnu contre une inconnue, la Justice et la société et les Médias donneraient ils la même importance qu’à Patrick Bruel par exemple?</p>\n<p>Faut-il être un criminel avec un nom et une classe sociale différente pour qu’un viol soit considéré grave et important?</p>\n<p>C’est tout ce que la France donne en guise de réponse par l’exercice de la pratique.</p>\n<p>Que ce soit dans les médias ou la Justice.</p>\n<p>La Justice de classe, elle est organisée et mise en place par les institutions, par l’État et son inertie chronique.</p>\n<p>Elle profite directement les prédateurs au détriment des victimes.</p>\n<p>Pendant que la presse mange des popcorns sur les décombres de vies brisées, pendant que nos sociétés sont plus intéressées par la révélation que par la souffrance des victimes, pendant que nos sociétés continuent d’asseoir les processus judiciaire sur des lois conçues pour protéger les prédateurs, des millions de cas de viols, d’abus sur des femmes et des enfants continuent.</p>\n<p>En toute <a href=\"https://chardonsbleus.org/\">impunité</a></p>\n",
      "content_text": "Chaque fois qu’éclate une affaire de viol en France, j’ai la même réflexion : et si c’était un viol d’un inconnu contre une inconnue, la Justice et la société et les Médias donneraient ils la même importance qu’à Patrick Bruel par exemple? Faut-il être un criminel avec un nom et une classe sociale différente pour qu’un viol soit considéré grave et important? C’est tout ce que la France donne en guise de réponse par l’exercice de la pratique. Que ce soit dans les médias ou la Justice. La Justice de classe, elle est organisée et mise en place par les institutions, par l’État et son inertie chronique. Elle profite directement les prédateurs au détriment des victimes. Pendant que la presse mange des popcorns sur les décombres de vies brisées, pendant que nos sociétés sont plus intéressées par la révélation que par la souffrance des victimes, pendant que nos sociétés continuent d’asseoir les processus judiciaire sur des lois conçues pour protéger les prédateurs, des millions de cas de viols, d’abus sur des femmes et des enfants continuent. En toute impunité",
      "date_published": "2026-05-20T16:58:34Z",
      "date_modified": "2026-05-20T16:59:36Z"
    },
    {
      "id": "https://rmendes.net/articles/2026/05/20/building-plume/",
      "url": "https://rmendes.net/articles/2026/05/20/building-plume/",
      "title": "Building Plume",
      "content_html": "<p>I got annoyed.</p>\n<p>Every time I wanted to bookmark a page or jot down a note, I’d open the Indiekit admin UI in a new tab, paste a URL, and click around. Small friction. But small friction repeats forever. So two days ago I started a brainstorm with Claude on what it would take to build a browser extension that posts to my Micropub blog directly from the toolbar.</p>\n<p>Two days later, Plume is live on the Chrome Web Store and Mozilla AMO. Source on GitHub. No telemetry, two browsers, 127 unit tests if you’re counting.</p>\n<p>Here’s how it happened.</p>\n<h2 id=\"what-plume-does\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"https://rmendes.net/articles/2026/05/20/building-plume/#what-plume-does\">What Plume does</a></h2>\n<p>It’s a Micropub client that lives in your browser’s toolbar. Click the feather icon and you get a composer for notes, articles, bookmarks, replies, likes, reposts, quotes, and photos. Multi-account if you run more than one blog. Right-click any page, link, image, or text selection to post about it with the right fields already filled in. Drafts auto-save while you type. Failed posts queue up and retry in the background. There’s a Markdown toolbar with a live preview, and the composer can pop out to a tab if you need desk-width room for an article. The only network requests Plume makes are to your own Micropub endpoint.</p>\n<p>Standard IndieWeb stuff, just packaged so it’s always one keystroke away. Default shortcut: <code>Alt+Shift+P</code>.</p>\n<h2 id=\"how-we-built-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"https://rmendes.net/articles/2026/05/20/building-plume/#how-we-built-it\">How we built it</a></h2>\n<p>I’d already written a Micropub MCP server — same family as Plume — so Claude could post to my blog directly. That codebase had everything Plume needed minus the browser layer: HTTP client for Micropub create/update/delete/query/upload, IndieAuth + PKCE, endpoint discovery. The work was wrapping it in something that runs inside Chrome and Firefox.</p>\n<p>Stack: WXT for the extension framework (Vite-based, generates Chrome and Firefox manifests from one config), Preact for the UI, TypeScript, Bun. Preact is small and feels like React minus the dependency cost. Bun runs TypeScript directly so there’s no build step for tests.</p>\n<p>The actual workflow was three skills stacked. First, a brainstorming skill that takes a one-line idea, walks me through clarifying questions, and produces a design document. Then a planning skill that turns the design into an implementation plan with exact files, code, and commit boundaries. Then a subagent-driven execution skill that dispatches a fresh agent per task, one per file or feature, with a review loop after each. Each agent only saw what it needed. I wasn’t managing thirty open files. That last part is the actual win — agentic coding only works if the human’s context stays clear too.</p>\n<p>Some things didn’t go to plan. Biome, the linter we picked first, crashed with SIGABRT twice in a row, so we migrated to ESLint + Prettier mid-build. Chrome Web Store rejected the first upload because we’d included a <code>manifest.key</code> field, which CWS refuses on initial submission (it assigns its own ID). Mozilla AMO then rejected v1.0.3 because Firefox extensions now have to declare data collection permissions in the manifest. Each one felt like a setback at the time. Each one resolved in under thirty minutes.</p>\n<p>Three decisions I’m happy with:</p>\n<p>The Markdown preview lazy-loads snarkdown and DOMPurify only when you click the preview button. Users who never preview pay nothing for the parser. Eager-loaded, the popup chunk was 47 kB. Lazy, it’s 21 kB with 27 kB of optional chunks. The popup feels instant.</p>\n<p>The composer pop-out is the same <code>popup.html</code> opened in a tab with a <code>?popout=1</code> flag. The component code doesn’t know or care which target it’s rendering into — only the layout cares. Toolbar popup at 360 px, tab view at 480–720 px. One file, two contexts.</p>\n<p>For IndieAuth, <code>chrome.identity.launchWebAuthFlow</code> handles the OAuth dance. The catch is that Chrome’s flow uses a per-extension callback URL on <code>*.chromiumapp.org</code>. To make the same extension work in dev and prod, we embed the Chrome Web Store’s production public key as <code>manifest.key</code> in dev mode. The dev install then derives the same extension ID as the published one, so one redirect URI declaration covers both.</p>\n<p>I also patched the upstream Indiekit endpoint-auth package so it can fetch a <code>client_id</code> URL and parse <code>&lt;link rel=&quot;redirect_uri&quot;&gt;</code> tags with wildcard subdomain matching. Plume’s redirect URI lives on <code>chromiumapp.org</code>, not on <code>rmendes.net</code>, and the existing validation rejected cross-host redirects.</p>\n<h2 id=\"releases\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"https://rmendes.net/articles/2026/05/20/building-plume/#releases\">Releases</a></h2>\n<p>v1.0.0 shipped on May 18. By the end of the next day we were at v1.2.0. The dot releases in between were mostly the store-publishing learning curve. 1.0.1 fixed a missing file picker on the photo tab and a context-menu race condition. 1.0.2 made <code>manifest.key</code> dev-only. 1.0.3 pinned the dev key to the CWS production key so callback URLs stayed stable. 1.0.4 added Firefox data collection permissions. Then 1.1.0 brought the MediaPicker (browse existing media on the server), the pop-out composer, server-side extension detection via <code>?q=post-types</code> property scanning, the keyboard shortcut, and live-updating queue and draft lists. 1.2.0 added the Markdown toolbar and preview.</p>\n<p>Shipping a tagged release every couple of hours is unreasonably motivating. Each version landed, I tested it on <a href=\"http://rmendes.net/\">rmendes.net</a>, found the next gap, and the next one shipped in an hour or two. The release workflow on GitHub Actions extracts the changelog section by tag and drafts a GitHub release with the Chrome and Firefox zips attached.</p>\n<h2 id=\"how-to-use-it\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"https://rmendes.net/articles/2026/05/20/building-plume/#how-to-use-it\">How to use it</a></h2>\n<p>Install from the Chrome Web Store or Mozilla AMO. Click the feather icon in your toolbar. The first time, it’ll say “No Micropub account connected.” Click “Open Plume settings,” paste your blog URL, click Authorize. Your IndieAuth server opens a consent page in a small Chrome window. Approve. Plume gets a token and stores it locally.</p>\n<p>From there: toolbar feather to write. Right-click a page to bookmark it. Right-click selected text to reply or quote with the passage as a Markdown blockquote. Right-click an image to post a photo. Settings any time to switch accounts, drain the retry queue, or recover a draft.</p>\n<h2 id=\"whats-next\" tabindex=\"-1\"><a class=\"header-anchor\" href=\"https://rmendes.net/articles/2026/05/20/building-plume/#whats-next\">What’s next</a></h2>\n<p>A few things on the list. Sending <code>content</code> as <code>{markdown: &quot;...&quot;}</code> explicitly instead of relying on Indiekit’s default markdown handling. Better cross-browser end-to-end coverage — right now E2E only runs on Chromium. Maybe a Firefox-specific design pass; the popup feels slightly tight there compared to Chrome.</p>\n<p>But the core thing is done. Plume is the tool I wanted: friction-free Micropub posting from any page, with my own blog as the destination. If you run Indiekit or another spec-compliant Micropub server, it should just work.</p>\n<p>Source: <a href=\"https://github.com/rmdes/plume\">github.com/rmdes/plume</a><br />\nLanding page: <a href=\"https://rmdes.github.io/plume/\">rmdes.github.io/plume</a></p>\n",
      "content_text": "I got annoyed. Every time I wanted to bookmark a page or jot down a note, I’d open the Indiekit admin UI in a new tab, paste a URL, and click around. Small friction. But small friction repeats forever. So two days ago I started a brainstorm with Claude on what it would take to build a browser extension that posts to my Micropub blog directly from the toolbar. Two days later, Plume is live on the Chrome Web Store and Mozilla AMO. Source on GitHub. No telemetry, two browsers, 127 unit tests if you’re counting. Here’s how it happened. What Plume does It’s a Micropub client that lives in your browser’s toolbar. Click the feather icon and you get a composer for notes, articles, bookmarks, replies, likes, reposts, quotes, and photos. Multi-account if you run more than one blog. Right-click any page, link, image, or text selection to post about it with the right fields already filled in. Drafts auto-save while you type. Failed posts queue up and retry in the background. There’s a Markdown toolbar with a live preview, and the composer can pop out to a tab if you need desk-width room for an article. The only network requests Plume makes are to your own Micropub endpoint. Standard IndieWeb stuff, just packaged so it’s always one keystroke away. Default shortcut: Alt+Shift+P. How we built it I’d already written a Micropub MCP server — same family as Plume — so Claude could post to my blog directly. That codebase had everything Plume needed minus the browser layer: HTTP client for Micropub create/update/delete/query/upload, IndieAuth + PKCE, endpoint discovery. The work was wrapping it in something that runs inside Chrome and Firefox. Stack: WXT for the extension framework (Vite-based, generates Chrome and Firefox manifests from one config), Preact for the UI, TypeScript, Bun. Preact is small and feels like React minus the dependency cost. Bun runs TypeScript directly so there’s no build step for tests. The actual workflow was three skills stacked. First, a brainstorming skill that takes a one-line idea, walks me through clarifying questions, and produces a design document. Then a planning skill that turns the design into an implementation plan with exact files, code, and commit boundaries. Then a subagent-driven execution skill that dispatches a fresh agent per task, one per file or feature, with a review loop after each. Each agent only saw what it needed. I wasn’t managing thirty open files. That last part is the actual win — agentic coding only works if the human’s context stays clear too. Some things didn’t go to plan. Biome, the linter we picked first, crashed with SIGABRT twice in a row, so we migrated to ESLint + Prettier mid-build. Chrome Web Store rejected the first upload because we’d included a manifest.key field, which CWS refuses on initial submission (it assigns its own ID). Mozilla AMO then rejected v1.0.3 because Firefox extensions now have to declare data collection permissions in the manifest. Each one felt like a setback at the time. Each one resolved in under thirty minutes. Three decisions I’m happy with: The Markdown preview lazy-loads snarkdown and DOMPurify only when you click the preview button. Users who never preview pay nothing for the parser. Eager-loaded, the popup chunk was 47 kB. Lazy, it’s 21 kB with 27 kB of optional chunks. The popup feels instant. The composer pop-out is the same popup.html opened in a tab with a ?popout=1 flag. The component code doesn’t know or care which target it’s rendering into — only the layout cares. Toolbar popup at 360 px, tab view at 480–720 px. One file, two contexts. For IndieAuth, chrome.identity.launchWebAuthFlow handles the OAuth dance. The catch is that Chrome’s flow uses a per-extension callback URL on *.chromiumapp.org. To make the same extension work in dev and prod, we embed the Chrome Web Store’s production public key as manifest.key in dev mode. The dev install then derives the same extension ID as the published one, so one redirect URI declaration covers both. I also patched the upstream Indiekit endpoint-auth package so it can fetch a client_id URL and parse &lt;link rel=&quot;redirect_uri&quot;&gt; tags with wildcard subdomain matching. Plume’s redirect URI lives on chromiumapp.org, not on rmendes.net, and the existing validation rejected cross-host redirects. Releases v1.0.0 shipped on May 18. By the end of the next day we were at v1.2.0. The dot releases in between were mostly the store-publishing learning curve. 1.0.1 fixed a missing file picker on the photo tab and a context-menu race condition. 1.0.2 made manifest.key dev-only. 1.0.3 pinned the dev key to the CWS production key so callback URLs stayed stable. 1.0.4 added Firefox data collection permissions. Then 1.1.0 brought the MediaPicker (browse existing media on the server), the pop-out composer, server-side extension detection via ?q=post-types property scanning, the keyboard shortcut, and live-updating queue and draft lists. 1.2.0 added the Markdown toolbar and preview. Shipping a tagged release every couple of hours is unreasonably motivating. Each version landed, I tested it on rmendes.net, found the next gap, and the next one shipped in an hour or two. The release workflow on GitHub Actions extracts the changelog section by tag and drafts a GitHub release with the Chrome and Firefox zips attached. How to use it Install from the Chrome Web Store or Mozilla AMO. Click the feather icon in your toolbar. The first time, it’ll say “No Micropub account connected.” Click “Open Plume settings,” paste your blog URL, click Authorize. Your IndieAuth server opens a consent page in a small Chrome window. Approve. Plume gets a token and stores it locally. From there: toolbar feather to write. Right-click a page to bookmark it. Right-click selected text to reply or quote with the passage as a Markdown blockquote. Right-click an image to post a photo. Settings any time to switch accounts, drain the retry queue, or recover a draft. What’s next A few things on the list. Sending content as {markdown: &quot;...&quot;} explicitly instead of relying on Indiekit’s default markdown handling. Better cross-browser end-to-end coverage — right now E2E only runs on Chromium. Maybe a Firefox-specific design pass; the popup feels slightly tight there compared to Chrome. But the core thing is done. Plume is the tool I wanted: friction-free Micropub posting from any page, with my own blog as the destination. If you run Indiekit or another spec-compliant Micropub server, it should just work. Source: github.com/rmdes/plume Landing page: rmdes.github.io/plume",
      "date_published": "2026-05-20T07:42:02Z",
      "date_modified": "2026-05-20T08:33:57Z"
    },
    {
      "id": "https://rmendes.net/bookmarks/2026/05/19/plume-micropub-for-browsers/",
      "url": "https://rmendes.net/bookmarks/2026/05/19/plume-micropub-for-browsers/",
      "title": "Plume — Micropub for browsers",
      "content_html": "",
      "content_text": "",
      "date_published": "2026-05-19T15:40:06Z",
      "date_modified": "2026-05-19T15:40:52Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/19/59c57/",
      "url": "https://rmendes.net/notes/2026/05/19/59c57/",
      "title": null,
      "content_html": "<p>Odd situation with <a href=\"https://buzzworkers.com/\">buzzworkers</a> LetsEncrypt certificate was not renewing properly, changed gandi API LiveDNS (deprecated) to PAT and forced cloudron to renew the certificate.\nProblem solved but in more than a decade its the first time a LE certificate does not renew properly on its own.</p>\n",
      "content_text": "Odd situation with buzzworkers LetsEncrypt certificate was not renewing properly, changed gandi API LiveDNS (deprecated) to PAT and forced cloudron to renew the certificate. Problem solved but in more than a decade its the first time a LE certificate does not renew properly on its own.",
      "date_published": "2026-05-19T14:55:51Z",
      "date_modified": "2026-05-19T14:56:30Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/19/b6d59/",
      "url": "https://rmendes.net/notes/2026/05/19/b6d59/",
      "title": null,
      "content_html": "<p>Hmm have to investigate why Bluesky sidebar widget is not updating anymore…</p>\n",
      "content_text": "Hmm have to investigate why Bluesky sidebar widget is not updating anymore…",
      "date_published": "2026-05-19T14:48:35Z",
      "date_modified": "2026-05-19T14:49:29Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/18/63bba/",
      "url": "https://rmendes.net/notes/2026/05/18/63bba/",
      "title": null,
      "content_html": "<p>Listening <a href=\"https://somafm.com/player24/station/gsclassic\">https://somafm.com/player24/station/gsclassic</a></p>\n",
      "content_text": "Listening https://somafm.com/player24/station/gsclassic",
      "date_published": "2026-05-18T18:42:58Z",
      "date_modified": "2026-05-18T18:43:41Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/18/2f716/",
      "url": "https://rmendes.net/notes/2026/05/18/2f716/",
      "title": null,
      "content_html": "<p>This is a test from my new Chrome/Firefox <a href=\"https://rmendes.net/categories/micropub/\" class=\"p-category hashtag\">#micropub</a> extension</p>\n",
      "content_text": "This is a test from my new Chrome/Firefox #micropub extension",
      "date_published": "2026-05-18T17:24:53Z",
      "date_modified": "2026-05-18T17:30:48Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/17/7252f/",
      "url": "https://rmendes.net/notes/2026/05/17/7252f/",
      "title": null,
      "content_html": "<p>Updated my <a href=\"https://rmendes.net/categories/bluesky/\" class=\"p-category hashtag\">#Bluesky</a> chrome/vivaldi/edge/brave <a href=\"https://chromewebstore.google.com/detail/kiddamcckmefboigpmhdemfccdbfmago?utm_source=item-share-cb\">extension</a> more info <a href=\"https://rmdes.github.io/oGraph-Bluesky-Poster/\">here</a> if you’re on <a href=\"https://rmendes.net/categories/firefox/\" class=\"p-category hashtag\">#firefox</a> <a href=\"https://github.com/rmdes/oGraph-Bluesky-Poster/releases/tag/v2.1.1\">install</a> the XPI extension on developer mode.</p>\n",
      "content_text": "Updated my #Bluesky chrome/vivaldi/edge/brave extension more info here if you’re on #firefox install the XPI extension on developer mode.",
      "date_published": "2026-05-17T11:07:13Z",
      "date_modified": "2026-05-17T11:13:29Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/05/22a19/",
      "url": "https://rmendes.net/notes/2026/05/05/22a19/",
      "title": null,
      "content_html": "<p>So I wanted to have a simple way to quickly find previous Claude Code sessions across different environments, in my case, two WSL instances, so I vibe coded this <a href=\"https://github.com/rmdes/claude-recent\">nice little TUI</a> tool.</p>\n<p>my itch was that I run claude sessions from different directories and I never remember exactly where, I didn’t wanted to change my work habit (always start claude from the same path) and I didn’t want to search which claude session have the right context, I also wanted to have my sessions properly named so that I could find ongoing sessions with ease, I wanted to be able to preview the sessions and I wanted to be able to backfill all my previous sessions so that I could list them with an easy way to get back into them with ease.</p>\n<p>Well, <a href=\"https://github.com/rmdes/claude-recent\">https://github.com/rmdes/claude-recent</a> is what I came up with.\nits probably similar to what other approach to solve this problem already do but I wanted to control exactly what the tool is doing.</p>\n<p>Now it’s helping me on my day to day work and offload to a simple tool a problem that was creating cognitive load, sometimes loss due to fragmentation.</p>\n<p>I’m pretty happy with the result even tho the tool is pretty rough.</p>\n<p>feedback welcome if you end up using it !</p>\n",
      "content_text": "So I wanted to have a simple way to quickly find previous Claude Code sessions across different environments, in my case, two WSL instances, so I vibe coded this nice little TUI tool. my itch was that I run claude sessions from different directories and I never remember exactly where, I didn’t wanted to change my work habit (always start claude from the same path) and I didn’t want to search which claude session have the right context, I also wanted to have my sessions properly named so that I could find ongoing sessions with ease, I wanted to be able to preview the sessions and I wanted to be able to backfill all my previous sessions so that I could list them with an easy way to get back into them with ease. Well, https://github.com/rmdes/claude-recent is what I came up with. its probably similar to what other approach to solve this problem already do but I wanted to control exactly what the tool is doing. Now it’s helping me on my day to day work and offload to a simple tool a problem that was creating cognitive load, sometimes loss due to fragmentation. I’m pretty happy with the result even tho the tool is pretty rough. feedback welcome if you end up using it !",
      "date_published": "2026-05-05T15:18:35Z",
      "date_modified": "2026-05-05T15:19:28Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/04/23f65/",
      "url": "https://rmendes.net/notes/2026/05/04/23f65/",
      "title": null,
      "content_html": "<p>Avec mon café du matin, je scroll 2 minutes la ligne du temps de ces dernières 48h et je constate que la dissonance cognitive c’est aussi aller au taf et s’occuper de sa vie comme si de rien n’était alors que le monde brûle et se consume dans d’incessantes révulsions qui me dépassent et pourtant… tout vas bien ! Bon Lundi, Bonne Semaine !</p>\n",
      "content_text": "Avec mon café du matin, je scroll 2 minutes la ligne du temps de ces dernières 48h et je constate que la dissonance cognitive c’est aussi aller au taf et s’occuper de sa vie comme si de rien n’était alors que le monde brûle et se consume dans d’incessantes révulsions qui me dépassent et pourtant… tout vas bien ! Bon Lundi, Bonne Semaine !",
      "date_published": "2026-05-04T07:03:15Z",
      "date_modified": "2026-05-04T07:04:45Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/05/02/1c1db/",
      "url": "https://rmendes.net/notes/2026/05/02/1c1db/",
      "title": null,
      "content_html": "<p>je rêve d’une lampe de chevet/projecteur reliée à la page Flickr de la NASA qui afficherait les dernières photos de l’univers en haute résolution sur le plafond de ma chambre.</p>\n<p>Malheureusement les options qui existent sont cadenassées a des images prédéfinies et limitées en nombre.</p>\n<p>il y a aussi le problème du bruit à régler, un mini projecteur serait trop bruyant sur une table de chevet.</p>\n",
      "content_text": "je rêve d’une lampe de chevet/projecteur reliée à la page Flickr de la NASA qui afficherait les dernières photos de l’univers en haute résolution sur le plafond de ma chambre. Malheureusement les options qui existent sont cadenassées a des images prédéfinies et limitées en nombre. il y a aussi le problème du bruit à régler, un mini projecteur serait trop bruyant sur une table de chevet.",
      "date_published": "2026-05-02T09:44:19Z",
      "date_modified": "2026-05-02T09:44:19Z"
    },
    {
      "id": "https://rmendes.net/reposts/2026/05/01/009d7/",
      "url": "https://rmendes.net/reposts/2026/05/01/009d7/",
      "title": null,
      "content_html": "<p>interesting read…</p>\n",
      "content_text": "interesting read…",
      "date_published": "2026-05-01T10:35:54Z",
      "date_modified": "2026-05-01T10:39:23Z"
    },
    {
      "id": "https://rmendes.net/notes/2026/04/26/9cc07/",
      "url": "https://rmendes.net/notes/2026/04/26/9cc07/",
      "title": null,
      "content_html": "<p>The Trump administration should be on trial, all of them, and face the war crimes they have perpetrated.\nAlong with Putin, Khamenei and Netanyahu. That would be a good Earth Day.</p>\n",
      "content_text": "The Trump administration should be on trial, all of them, and face the war crimes they have perpetrated. Along with Putin, Khamenei and Netanyahu. That would be a good Earth Day.",
      "date_published": "2026-04-26T18:32:44Z",
      "date_modified": "2026-04-26T18:34:35Z"
    }
  ]
}
