<?xml version="1.0" encoding="UTF-8" ?>
    <rss
        version="2.0"
        xmlns:atom="http://www.w3.org/2005/Atom"
        xmlns:content="http://purl.org/rss/1.0/modules/content/"
    >
    <channel>
        <title>Matteo Marjanovic - blog</title>
        <description>Articles written by Matteo Marjanovic on his blog.</description>
        <link>https://matteo-marjanovic-svelte.netlify.app</link>
        <atom:link href="https://matteo-marjanovic-svelte.netlify.app/api/en/rss" rel="self" type="application/rss+xml"/>
        
                <item>
                    <title>Into the feed of Currents, an open Pinterest alternative built on AT Protocol</title>
                    <link>https://matteo-marjanovic-svelte.netlify.app/blog/en/currents-feed-how-it-works</link>
                    <description>In this article I describe how I built the first version of Currents' feed, an open Pinterest alternative on AT Protocol, and how its personalization slider works.</description>
                    <content:encoded><![CDATA[<!--[--><p>Over the past few weeks, I’ve been inspired by the many developers building new social apps on top of <a href="https://bsky.app/" rel="nofollow">Bluesky</a>’s <a href="https://atproto.com/" rel="nofollow">AT Protocol</a>, and I decided to start my own project: <a href="https://currents.is/?utm_source=matteomarjanovic.com&amp;utm_medium=link" rel="nofollow">Currents</a>, an open and decentralized alternative to Pinterest.<br/> I published it a few days ago, still in “alpha”, and the code is available at <a href="https://github.com/matteomarjanovic/currents" rel="nofollow">this link</a>. I’d also like to use this project as an open, public laboratory where I can experiment with topics that interest me but that I don’t get to explore in my day job.<br/> One of those areas is <em>recommendation systems</em>. In this article I want to describe how I built the first version of <a href="https://currents.is/explore" rel="nofollow">Currents’ feed</a>, which has the distinctive feature of being more or less personalized depending on the user’s preferences.</p> <img src="/currents-slider.gif" alt="Currents feed personalization slider demo"/> <p>With this slider, users can adjust a parameter — which we’ll call α — that controls the level of personalization of the feed. Here’s an overview of how the system works.</p> <h2>From pixels to embeddings</h2> <p>The first part of the system transforms every saved image into an embedding: a 768-dimensional vector that encodes the meaning of the image. Images are mapped into a shared 768-dimensional space, where vectors representing similar images are close together, and vectors corresponding to semantically distant images are far apart.</p> <p>These vectors are stored in a column of a PostgreSQL database (using the <a href="https://github.com/pgvector/pgvector" rel="nofollow">pgvector</a> extension), which, thanks to an <a href="https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world" rel="nofollow">HNSW</a> index, allows efficient retrieval of the nearest vectors to a given input.<br/> The model used to produce these embeddings is <a href="https://huggingface.co/blog/siglip2" rel="nofollow">SigLIP 2</a>, which has a notable property: it can project both images and text into the same vector space. This means it’s possible to do text-based image search by turning a query string into an embedding and using it to retrieve the most similar image vectors.</p> <h2>Personalized feed</h2> <p>To build a personalized feed, I drew inspiration from <a href="https://arxiv.org/abs/2007.03634" rel="nofollow">PinnerSage</a> — Pinterest’s recommendation system published in 2020 — and from how <a href="https://recsysml.substack.com/p/personalization-at-bluesky" rel="nofollow">Bluesky’s Discover feed</a> is generated, with some simplifications to keep Currents as lean as possible.</p> <p>Both of those approaches run a clustering algorithm on embeddings to create groups of <em>pins</em> or <em>posts</em>, from which representative items of the user’s taste are drawn. Instead of running a dedicated clustering step, I decided to use the user’s own collections as clusters. Every time a user saves a new image to a collection, the medoid of that collection’s embeddings is recalculated — that is, the item that minimizes the sum of squared distances to all other items in the collection — and used as the representative embedding for that collection. Using the medoid rather than the mean of the vectors ensures that the representative always corresponds to a real image the user chose to save.</p> <p>To generate the personalized feed, the medoids of the user’s 3 most “important” collections are taken, and a similarity search is run across all embeddings in the database, retrieving the items most similar to those 3 reference points. To determine which collections are most “important”, I used the same formula as PinnerSage, which weights saves by recency:</p> <div class="my-6 w-full overflow-x-auto text-center text-lg font-serif">Importance(C, λ) = ∑<sub>i∈C</sub> e<sup>-λ(T<sub>now</sub> - T[i])</sup></div> <p>One downside of this approach is that users don’t always organize their collections by topic — for example, someone might create a collection called “2026” containing everything they saved that year — meaning the collection medoids may not be truly representative. Using a dedicated clustering algorithm could be more effective; that’s an experiment I plan to run in the future, once Currents has enough users to measure the impact. For now, this is the simplest and most practical approach I’ve found.</p> <h2>Serendipity</h2> <p>On the opposite end of the slider from the personalized feed, there’s the label <em>New worlds</em>. When the slider is moved toward that end, the feed follows a concept called <em>serendipity</em>.</p> <p>Most recommendation systems we use today optimize for accuracy: they try to show us more and more of what we already like. A well-known downside of this approach is the <em>filter bubble</em> problem: users end up seeing only content that fits their existing tastes, limiting exposure to anything new or different.</p> <p>For a creative person, I think this problem is especially relevant. Serendipity aims to solve it by maximizing recommendations that are unexpected yet still somehow relevant.</p> <p>While it’s difficult to measure serendipity objectively, I decided to build a system that follows this principle and use it as a baseline for future experimentation.</p> <p>The implementation is straightforward: once a day, a clustering pass is run over the entire image space to group images by similarity. (Dimensionality is first reduced to 50 dimensions using <a href="https://umap-learn.readthedocs.io/en/latest/" rel="nofollow">UMAP</a>, and then <a href="https://hdbscan.readthedocs.io/en/latest/how_hdbscan_works.html" rel="nofollow">HDBSCAN</a> is applied for clustering, mitigating the so-called <em>curse of dimensionality</em>.)<br/> Feed generation then works similarly to the personalized feed, with one difference in the 3 reference embeddings: instead of using the medoids of the 3 most important collections, for each of those collections we take the medoid of the nearest cluster whose items the user has never saved.<br/> This forces the recommendation of items from clusters the user hasn’t explored, while keeping them adjacent to their known interests.</p> <h2>The α parameter</h2> <p>The two methods described above correspond to the two extremes of the slider’s spectrum. The slider controls α, which ranges from -1 to 1 and can take any value in between. This allows blending recommendations with a global feed: for example, with α=0.4, 40% of the suggested images come from the personalized feed and 60% from the global feed; with α=-0.7, 70% come from the serendipitous feed and 30% from the global feed.<br/> The global feed ranks images by novelty and popularity using the following formula:</p> <div class="my-6 w-full overflow-x-auto text-center text-lg font-serif">score = saves · e<sup>-0.01 · age</sup></div> <h2>Conclusions</h2> <p>This project will be a personal laboratory for experimenting with interesting ideas, but I also hope it becomes an app that genuinely solves problems that users of similar apps face. I’ll keep listening to make it something that truly works for them.<br/> Thanks for reading :)<br/> Matteo</p> <hr/> <p>[1] Pal, Aditya, Chantat Eksombatchai, Yitong Zhou, Bo Zhao, Charles Rosenberg, and Jure Leskovec. “PinnerSage: Multi-Modal User Embedding Framework for Recommendations at Pinterest.” <em>Proceedings of the 26th ACM SIGKDD International Conference on Knowledge Discovery &amp; Data Mining</em>, July 7, 2020, 2311–20. <a href="https://arxiv.org/abs/2007.03634" rel="nofollow">https://arxiv.org/abs/2007.03634</a></p> <p>[2] Wesley-Smith, Ian. “Personalization at Bluesky: The past, present, and future of personalization of the Discover feed.” <em>RecsysML + LLMs</em>, February 23, 2026. <a href="https://recsysml.substack.com/p/personalization-at-bluesky" rel="nofollow">https://recsysml.substack.com/p/personalization-at-bluesky</a></p> <p>[3] <em>pgvector — Open-source vector similarity search for Postgres</em>. GitHub. <a href="https://github.com/pgvector/pgvector" rel="nofollow">https://github.com/pgvector/pgvector</a></p> <p>[4] <em>Hierarchical navigable small world (HNSW)</em>. Wikipedia. <a href="https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world" rel="nofollow">https://en.wikipedia.org/wiki/Hierarchical_navigable_small_world</a></p> <p>[5] Roy Gosthipaty, Aritra, Merve Noyan, and Pavel Iakubovskii. “SigLIP 2: A better multilingual vision language encoder.” <em>Hugging Face Blog</em>, February 21, 2025. <a href="https://huggingface.co/blog/siglip2" rel="nofollow">https://huggingface.co/blog/siglip2</a></p> <p>[6] McInnes, Leland, John Healy, and James Melville. “UMAP: Uniform Manifold Approximation and Projection for Dimension Reduction.” <em>arXiv:1802.03426</em>, February 9, 2018. <a href="https://arxiv.org/abs/1802.03426" rel="nofollow">https://arxiv.org/abs/1802.03426</a></p> <p>[7] McInnes, Leland, John Healy, and Steve Astels. “hdbscan: Hierarchical density based clustering.” <em>Journal of Open Source Software</em>, 2(11), 205, March 21, 2017. <a href="https://doi.org/10.21105/joss.00205" rel="nofollow">https://doi.org/10.21105/joss.00205</a></p><!--]-->]]></content:encoded>
                    <pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate>
                    <guid>https://matteo-marjanovic-svelte.netlify.app/blog/en/currents-feed-how-it-works</guid>
                </item>
                
                <item>
                    <title>How I added a comments section to my blog, using Bluesky</title>
                    <link>https://matteo-marjanovic-svelte.netlify.app/blog/en/add-comments-blog-bluesky</link>
                    <description>In this article I tell the story of how I created Juttu, an open source software that lets you use Bluesky posts as comments for your blog.</description>
                    <content:encoded><![CDATA[<!--[--><p>A few months have passed since I first started feeling the need for a space where I could write about what I think and what I do. Within a few days, that urge — paired with the desire to explore SvelteKit as a full-stack framework — gave shape to this blog.</p> <p>I was very happy with the elegant, minimal look I had achieved after just a few iterations. The only missing piece was a comments section at the bottom of each article: the thought that what I write here could spark interesting discussions is perhaps the thing that drives me most to write on the web.</p> <p>And that’s how I ended up in an endless cycle of research — the kind I dive into every time I’m faced with the burden of choosing a tool or service for some new project. This time, though, it didn’t take me long to realize the choice was almost a given: Disqus, born in 2007, spread quickly and became the most widely used service by online publications of all sizes. That’s probably why I already knew about it, and my research only served to reinforce some of the opinions I had already formed over time.</p> <p>To begin with, Disqus’s main revenue source is advertising. This means that, as highlighted in <a href="https://www.byrosanna.co.uk/blog/watch-out-for-ads-disqus-comments-have-gone-premium" rel="nofollow">this</a> and <a href="https://ryansouthgate.com/goodbye-disqus/" rel="nofollow">this</a> article (and there are countless others online), blog pages turn into full-blown ad boards over which the author has absolutely no control. On top of being aesthetically unpleasant and yet another sneaky way for tech companies to harvest data, this also takes a toll on performance: every time an article loads, a barrage of ads (with their links, images, and videos) must be downloaded by the reader’s browser, inevitably degrading the experience.</p> <p>Moreover, something many people overlook is the ownership of comments, which live on Disqus’s servers. This means that neither the author nor the readers have full control over what happens to them: they could all vanish overnight for no apparent reason.</p> <p>During my research I discovered several alternatives that try to address some of these issues (for example, <a href="http://commento.io" rel="nofollow">Commento</a> and <a href="https://talk.hyvor.com/" rel="nofollow">Hyvor Talk</a>), but it was when I stumbled upon <a href="https://emilyliu.me/blog/comments" rel="nofollow">this</a> article by Emily Liu that a quirky idea began to take shape. Emily was, at the time, “Head of Special Projects” at Bluesky, a social network that positions itself as an alternative to Twitter (now X) with some interesting peculiarities.</p> <p>As explained in her article, and in <a href="https://emilyliu.me/blog/bluesky-for-elections" rel="nofollow">other</a> <a href="https://emilyliu.me/blog/open-network" rel="nofollow">posts</a> on the same blog, Bluesky is a decentralized and open social network. Decentralized in the sense that user data lives across multiple servers, and users themselves can handle it as they please, moving it from one server to another with complete freedom. Open in the sense that all of Bluesky’s source code is open source, and its APIs are free and available to everyone.</p> <p>These are perfect conditions for using Bluesky posts as comments. All you have to do is publish a post on Bluesky mentioning that you’ve written a new article on your blog and then, with a simple call to the Bluesky API from the article’s page, you can display all replies to that post on the same page, as if they were comments on the article itself. Other people (see <a href="https://www.coryzue.com/writing/bluesky-comments/" rel="nofollow">this</a> and <a href="https://capscollective.com/blog/bluesky-blog-comments/" rel="nofollow">this</a> article) had started adopting the same approach on their blogs, so, intrigued, I decided to try integrating it into my embryonic blog — and I immediately noticed at least two limitations. The process of adding a new article became cumbersome: first I publish the article, then I publish the post linking to the article, and then I use the post’s identifier to republish the article with the correct Bluesky API call. A real hassle. On top of that, readers couldn’t authenticate directly in the comments section to interact (leave a like, repost, or reply with a comment) — they had to open the post on Bluesky to do so. Too much friction for a reader who simply wants to join the conversation.</p> <p>So I figured I could solve these problems and, after a few months of tinkering, today I published <a href="https://juttu.app/" rel="nofollow">Juttu</a>, an open source software that follows the same logic but addresses the issues I just mentioned. You can install it by copying these three lines of HTML, using your own DID and giving the article an ID of your choice (for example, “my-first-article”).</p> <!----><pre class="shiki one-dark-pro" style="background-color:#282c34;color:#abb2bf" tabindex="0"><code><span class="line"><span style="color:#7F848E;font-style:italic">&#x3C;!-- in &#x3C;head> --></span></span>
<span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">link</span><span style="color:#D19A66"> rel</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"site.standard.document"</span><span style="color:#D19A66"> href</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"at://YOUR_DID/site.standard.document/YOUR_ARTICLE_SLUG"</span><span style="color:#ABB2BF"> /></span></span>
<span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">script</span><span style="color:#D19A66"> defer</span><span style="color:#D19A66"> src</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"https://cdn.jsdelivr.net/npm/juttu@latest/juttu-embed.js"</span><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">script</span><span style="color:#ABB2BF">></span></span>
<span class="line"></span>
<span class="line"><span style="color:#7F848E;font-style:italic">&#x3C;!-- in &#x3C;body> --></span></span>
<span class="line"><span style="color:#ABB2BF">&#x3C;</span><span style="color:#E06C75">div</span><span style="color:#D19A66"> id</span><span style="color:#ABB2BF">=</span><span style="color:#98C379">"juttu-comments"</span><span style="color:#ABB2BF">>&#x3C;/</span><span style="color:#E06C75">div</span><span style="color:#ABB2BF">></span></span></code></pre><!----> <p>The result is visible in the comments section below, while the code — which you can find in <a href="https://github.com/matteomarjanovic/juttu" rel="nofollow">this GitHub repo</a> — can be browsed, forked, modified, and self-hosted freely by anyone. The documentation is available at <a href="https://docs.juttu.app/" rel="nofollow">this link</a>.</p> <p>While creating Juttu I got to know the community called “ATmosphere,” the community of applications built on AT Protocol — the open protocol created by Bluesky to allow building applications with “social” features that share the characteristics mentioned earlier (open and decentralized), and more. Inspired by what the members of this community are building, I’d also love to try my hand at creating a social network based on AT Protocol, but that’s material for another article.</p> <p>What’s certain is that I’ll commit to refining and improving Juttu to make it a solid alternative to Disqus. The first topic I’ll tackle is comment moderation: on this front, Bluesky and AT Protocol provide dedicated tools that I’ll need to figure out how to integrate. Let me know if you try installing Juttu on your blog — any feedback is more than welcome at this early stage.</p> <p>Thanks for reading :)</p> <p>Matteo</p> <hr/> <p>[1] Rosanna. “Watch Out for Ads! Disqus Comments Have Gone ‘Premium’.” <em>By Rosanna</em>, April 16, 2018. <a href="https://www.byrosanna.co.uk/blog/watch-out-for-ads-disqus-comments-have-gone-premium" rel="nofollow">https://www.byrosanna.co.uk/blog/watch-out-for-ads-disqus-comments-have-gone-premium</a></p> <p>[2] Southgate, Ryan. “Goodbye Disqus — Your Injected Ads Are Horrible.” <em>ryansouthgate.com</em>, September 30, 2025. <a href="https://ryansouthgate.com/goodbye-disqus/" rel="nofollow">https://ryansouthgate.com/goodbye-disqus/</a></p> <p>[3] <em>Commento — Add Comments to Your Website</em>. Commento.io. <a href="https://commento.io" rel="nofollow">https://commento.io</a></p> <p>[4] <em>Hyvor Talk — Privacy-First Commenting Platform</em>. Hyvor. <a href="https://talk.hyvor.com/" rel="nofollow">https://talk.hyvor.com/</a></p> <p>[5] Liu, Emily. “Using Bluesky Posts as Blog Comments.” <em>emilyliu.me</em>, November 24, 2024. <a href="https://emilyliu.me/blog/comments" rel="nofollow">https://emilyliu.me/blog/comments</a></p> <p>[6] Liu, Emily. “How Your Newsroom Can Use Bluesky This Election Season.” <em>emilyliu.me</em>, January 24, 2024. <a href="https://emilyliu.me/blog/bluesky-for-elections" rel="nofollow">https://emilyliu.me/blog/bluesky-for-elections</a></p> <p>[7] Liu, Emily. “Benefits of an Open Network.” <em>emilyliu.me</em>, November 24, 2024. <a href="https://emilyliu.me/blog/open-network" rel="nofollow">https://emilyliu.me/blog/open-network</a></p> <p>[8] Zue, Cory. “Adding Bluesky-Powered Comments to Any Website in Five Minutes.” <em>coryzue.com</em>, November 25, 2024. <a href="https://www.coryzue.com/writing/bluesky-comments/" rel="nofollow">https://www.coryzue.com/writing/bluesky-comments/</a></p> <p>[9] Moallem, Jonathan. “The Absolute Simplest Way to Use Your Bluesky Posts as a Blog Comment Backend.” <em>Caps Collective</em>, November 26, 2024. <a href="https://capscollective.com/blog/bluesky-blog-comments/" rel="nofollow">https://capscollective.com/blog/bluesky-blog-comments/</a></p><!--]-->]]></content:encoded>
                    <pubDate>Wed, 18 Mar 2026 00:00:00 GMT</pubDate>
                    <guid>https://matteo-marjanovic-svelte.netlify.app/blog/en/add-comments-blog-bluesky</guid>
                </item>
                
    </channel>
    </rss>