← Back


RSS feed

This is a place where I write what I built, what I made or what I worked on. It's the changelog.md of my work life.

Added support for data collections written in YAML to Darkmatter.


Signed up for Ahrefs and fixed the SEO issues it reported on Darkmatter’s website:

  • Removed duplicate URLs with a trailing slash in the sitemap.
  • Optimized images.
  • Updated all pages to have only one h1 tag.
  • Improved site description.

Presented Darkmatter to a small enthusiastic crowd in Astro’s weekly community call on Discord.

I was a bit nervous, because I got a spontaneous invitation and I wasn’t prepared for this at all. I drafted up some talking points and waited for my turn to speak.

Surprisingly, it went very well. I kept the whole speech to around 10 minutes, which covered literally every aspect of Darkmatter. People were commenting how much they liked it and they generally seemed exciting to check it out. One person even wanted to record a video about it, which would be quite cool!

Launched Darkmatter.

Rebuilt the license manager Rails app from scratch with a significantly reduced scope. It’s no longer built as a SaaS, so I don’t need to implement authentication, user management and make the features generic enough to adapt to any app.

Instead, I built a small app that works with Darkmatter and can support my other apps, which all have a similar pricing model.

It also includes a customer portal, where users can log in to manage their licenses. They can deactivate devices, buy more seats or renew their license for another year.

My new plan is to use this for Darkmatter for a while, integrate it into Lotus, Rosefinch and Linkjar and only then consider the possibility of opening it to everyone.

Updated Darkmatter to be compatible with the newly released Astro 3.0.


Published a newsletter and a blog post about latest Darkmatter updates: dark mode and license manager.

Shipped dark mode in Darkmatter.

CleanShot 2023-08-27 at 12.35.41@2x.png

CleanShot 2023-08-27 at 12.29.11@2x.png

Built a small Rails app to handle license keys for Darkmatter. I considered using Keygen, but I wanted an app that integrates with Paddle and offers an SDK to quickly integrate licensing into all my apps.

It has some nice features:

  • There’s a built-in webhook endpoint that can be set in Paddle to automatically generate license keys when someone buys my apps.
  • It supports limiting the number of machines each license can be activated on.
  • It provides an SDK to quickly integrate free trials and licensing into any Electron app, which is annoying to do manually every time.

I plan to use it in Darkmatter at first and then integrate it into my other apps, Rosefinch, Lotus and Linkjar.

If everything goes well, I will launch it as a standalone product.

Published a newsletter and a blog post about Darkmatter v0.10.0 release.

Shipped Darkmatter 0.10.0 with support for:

  • UI to commit and push your changes without leaving Darkmatter.
  • Automatically start/stop the Astro dev server to preview your changes in the browser.
  • Defining multi-line text fields and date time fields
  • Customizing the entry preview URLs.

There’s a new “Publish changes” button in the top right corner, which opens a simple dialog to commit and push your changes to git.

CleanShot 2023-08-19 at 11.53.59@2x.png

You don’t need to go back to terminal to run git commit and git push after writing in Darkmatter anymore. This is super convenient, because the entire content editing workflow can happen inside Darkmatter end to end.

In previous versions of Darkmatter, you’d need to start the Astro dev server manually, otherwise Darkmatter wouldn’t open the entry page in the browser.

Now Darkmatter starts the Astro dev server on a random port if it’s not running already. That way, there’s always a dev server to preview your content in and you don’t have to jump between Darkmatter and terminal.

Added a way to customize the entry URLs of each content collection in Darkmatter.

Darkmatter assumes that each entry can be viewed at /[collection name]/slug URL, but that’s not always the case. For example, I have a “posts” content collection, but posts are displayed on a /blog page. If I try to open any post in browser from Darkmatter, it’ll show a 404.

To fix that, there’s a new defineDarkmatterCollections() function in darkmatter-sdk package, which can configure the right base path for each collection.

import {z as zod, defineCollection} from 'astro:content';
import {defineDarkmatterCollections} from 'darkmatter-sdk';

const posts = defineCollection({
  schema: zod.object({
    title: zod.string()

export const collections = {posts};

export const darkmatter = defineDarkmatterCollections({
  posts: {
    basePath: '/blog'

Now Darkmatter would correctly open a /blog/[slug] route for each entry.

Added an RSS feed for the worklog you’re reading. Christopher, a reader of Darkmatter’s newsletter, emailed me asking about it, so I figured why not and shipped it.

Now I’ve got one RSS feed for my blog posts and one exclusively for worklogs. I added different <link rel="alternate"> tags for them, so your RSS reader should auto-detect the right RSS feed based on the page URL you’re giving it.

Using the same technique to implement text fields, I also added support for date time fields. It’s the same zod.date() field, but with an option to set the time, which will be saved to frontmatter as ISO 8601 timestamp.

import {z as zod, defineCollection} from 'astro:content';
import {dateTime} from 'darkmatter-sdk';

const posts = defineCollection({
  schema: zod.object({
    title: zod.string(),
    date: dateTime()

CleanShot 2023-08-15 at 09.26.59@2x.png

Shipped support for multi-line text field in Darkmatter.

This was tricky, because field types are inferred from the Zod schema, which doesn’t have a zod.string() that somehow hints that a string needs to have multiple lines.

import {z as zod, defineCollection} from 'astro:content';

const posts = defineCollection({
  schema: zod.object({
    title: zod.string(),
    description: zod.string()

CleanShot 2023-08-15 at 09.21.10@2x.png

To work around this limitation, I published a darkmatter-sdk package that exposes a text() function. It returns the same ZodString object, but Darkmatter detects when text() is used instead of zod.string() and marks the field as multi-line text.

import {z as zod, defineCollection} from 'astro:content';
import {text} from 'darkmatter-sdk';

const posts = defineCollection({
  schema: zod.object({
    title: zod.string(),
    description: text()

CleanShot 2023-08-15 at 09.20.18@2x.png

Released a massive v0.9.0 update to Darkmatter:

  • Support for union fields in collection schemas defined with zod.or().
  • Major changes to zod.date() fields, which are now saved as calendar dates (e.g. 2023-05-15) instead of ISO 8601 timestamps.
  • Introduction of macOS-style focus rings and extensive polishing of every UI element.
  • Collection search.

I worked on keyboard navigation in Darkmatter and decided it’s be a good idea to design macOS-like focus rings.

It may not be a 1:1 replica, but I think it’s pretty close. Another one of those details that make my Electron apps feel more like native ones.

CleanShot 2023-08-10 at 10.02.27.gif

Added support for editing union fields in Darkmatter, which are defined by zod.or() function.

	schema: zod.object({
		title: zod.string(),
		date: zod
			.transform(date => new Date(date))

CleanShot 2023-08-10 at 11.30.09@2x.png

Darkmatter detects the field type for the current value among the allowed types specified by the schema. It even lets you switch the field type if needed.

CleanShot 2023-08-10 at 11.30.20@2x.png

Sent out a newsletter and published a blog post about internationalization support in Darkmatter.

Shipped i18n support in Darkmatter, which allows content in Astro websites to have translations in multiple languages.


I wasn’t sure how to implement it at first, since there’s no API or configuration for it, just a set of recommendations from Astro on how to set it up with existing building blocks. I’d have to resort to detecting if folders are named after language codes, which sounded brittle.

However, I decided to give this shot, because this approach doesn’t require any work from the user of Darkmatter and it just magically detects content collection to have translated content.

No regrets so far, but I’ll see how it goes as more people use it. Big fan of that “Language” switch though.


Built a small app I called “selfie” to take screenshots of my vadimdemedes.com and getdarkmatter.dev websites to use them as Open Graph images.

CleanShot 2023-07-30 at 11.07.12@2x.png

I implemented it earlier as an Astro integration, but that implementation started to annoy the hell out of me. It required manually running a script before pushing to a git repository, otherwise screenshots would be missing or outdated.

This new app looks at the sitemap and screenshots all the pages it has found every time I deploy. It’s written in Node.js, hosted on Railway and uses Redis both for background workers and screenshot storage. Perhaps it could grow to a standalone product some day, who knows.

Sent out a newsletter and published a blog post about Darkmatter. Talked about recent updates in the 0.6.0 version.

Released Darkmatter 0.6.0 with support for data collections, new Astro assets API, native app menu and ability to open multiple project windows at once.

Implemented support for the new Astro assets API in Darkmatter.