enumerator.dev

Introducing willow.camp

Cassia Scheffer

Published on June 28, 2025

I’ve been working on a side project and am finally ready to share it! My blog, enumerator.dev, is now hosted on my platform called willow.camp.

willow.camp started because I wanted a platform that felt like me. I wanted a simple editing interface, the ability to import and export every post in markdown, and a pared-down reading interface.

My blog was previously hosted on Ghost, but the experience felt like an “influencer” blog to me. I’m a regular developer sharing the projects I work on, and I don’t need a big newsletter distribution list or metrics on open rates.

I looked at bearblog.dev and pika.page as alternatives to Ghost. They are both lovely platforms, and willow.camp is inspired by them, but they didn’t quite meet my needs. So I took the plunge and typed rails new willow_camp in my terminal.

Well, okay, I admit. First, I tried Elixir; then I read about doing it in Rust and tested out Hanami before realizing that I wanted to create something more than I wanted to learn a new language.

Sometimes speed is about using a batteries-included framework more than it is about milliseconds of server render time.

The willow.camp Stack

willow.camp is built on Rails 8, Turbo, Hotwire, Stimulus, and DaisyUI. These libraries have made it easy to spin up scaffolding and prototype ideas. A considerable part of the initial commits in willow.camp are all rails scaffold (not even AI!). As things took shape, I polished off features using context7 to pull docs into Zed hooked up to GitHub Copilot.

So far, here are a few libraries I’ve used to develop willow.camp:

Hosting

willow.camp is hosted on a DigitalOcean instance with a managed Postgres database. Deployments and configs are managed through hatchbox.io.

APM, Errors, Emails

I hooked up Scout Monitoring for APM and Honeybadger for error tracking. For now, I’m well below the usage limit for both, so I’m on the free plan. I’m happy with both of those, but I find the log management in Scout to be somewhat clunky on my tiny MacBook Air’s screen. I’m not sure I’d pay for the logging aspects of Scout, but everything else meets my needs.

Password reset emails are sent through loops.so. Loops was straightforward to set up, and transactional emails are free.

Dashboard

As mentioned above, the editing interface is marksmith, an easy-to-use, plug-and-play markdown editor with a built-in preview feature. Posts are saved in both markdown and HTML formats. This makes exporting content through the API straightforward because Markdown is the base format in which posts are written and saved. HTML is only used for display.

Mermaid Diagrams

You can add a code block with mermaid as the language, which will render a mermaid diagram on the page in place of the code block. Have a look at How Rack::Timeout Keeps Your App Alive for an example.

Auto-saving Posts

I like to start a draft post, then walk away and think about it. While I enjoy writing in Obsidian, I often use my blog “drafts” as a place to store ideas.

I added a stimulus controller that auto-saves draft posts. It checks if the post is “dirty” (i.e., changed since the last save) and saves it every 60 seconds. You can also hit Command/Control + S to save while you type.

Auto-save is disabled on “published” posts because I wouldn’t want a half-edited post to be saved and published.

No Image Hosting

I don’t host images or static content aside from what’s in the Rails asset pipeline. This is intentional right now. I want to keep hosting complexity to a minimum, and I didn’t want to worry about storage services, mounting volumes, or data retention. For now, I’d like the server to be ephemeral.

Favicons, Themes, Subdomains, & Custom Domains

Of course, I spent the most time on the settings page.

You can preview custom favicons and themes on the willow.camp home page by clicking the menu items in the top right. The themes come from DaisyUI. The favicon functionality still requires some refinement.

How willow.camp (sort of) Generates Favicons

Users can insert any emoji into the “favicon” settings form. The emoji is saved to their settings, and then when the page loads, it is rendered in in-memory canvases to generate the various sizes of favicons needed and insert them into the head as an image in a URI scheme like so:

<link type="image/png" rel="icon" sizes="16x16" href="data:image/png;base64,...lots of characters=">

This works for most cases, but since the image doesn’t exist until the JS renders it, some browsers may not display a favicon at all, and there are no Open Graph images available for link previews on social sites.

Better Favicons in the Future

I want to fix the favicon functionality. I think the best way to do this reliably is to pre-generate images for 10-20 emojis that users can pick from. This way, I only have to store a set number of assets on the server and can provide real links to favicon images.

Another possibility is to render a canvas in the browser when the user selects the desired emoji. Then, when they update their settings, save the favicon as an attachment and generate the correct sizes on the server. This would provide flexibility to use any emoji, but it would also require hosting more static content, which I didn’t want to do yet.

Themes

The DaisyUI themes are fun! I borrowed their theme picker HTML and used a Stimulus controller to give instant previews of each theme. Users’ theme preferences are saved in the DB and rendered in the HTML.

I experimented with allowing users to choose between a light and dark theme, but couldn’t get it to work to my satisfaction. prefers-colour-scheme works great with DiasyUI’s default light and dark, but I didn’t see an easy way to pass a user’s preferences from the database into the CSS without compiling the assets on each request.

I did have a light/dark functionality sort of working with a Stimulus controller and some data attributes in the HTML, but there was a brief flash of the default light or dark scheme before the chosen scheme was loaded.

For now, you can pick one colour scheme for your willow.camp blog.

Subdomains and Custom Domains

Thanks to hatchbox’s Caddy setup, subdomains and custom domains are served over HTTPS. Each blog has a subdomain like cassia.willow.camp and an optional custom domain like enumerator.dev. Users point a CNAME record to their subdomain to set up a custom domain name.

SEO

It has been nearly a decade since I had to think about SEO. I’ve added Open Graph tags to the pages, and every site gets a sitemap.xml and robots.txt. I tried to use semantic HTML as best I could and asked Claude to audit the pages.

No Analytics

I had plausible.io on my blog for a very brief period, but the analytics provided too much information for me. I just want to share what I write. I don’t need graphs.

If you want to use willow.camp for your blog and want analytics, let me know, and I can add a feature to let you insert the code you need.

Accessibility

I need to do another accessibility audit on willow.camp. I tried to ensure the HTML used aria labels that were clear and concise.

I recently found CapybaraAccessibilityAudit, which I’ll use to audit the dashboard and public-facing pages.

About Page

The “about” page is a bit of a hack. “Pages” and “Posts” are almost the same thing in the database. I used single table inheritance to create a single “about” page for each user. Every user always has one about page. If you delete your “about” page, a new draft page is created in the background.

willow.camp doesn’t use pages otherwise. Everyone gets one “about” page.

Feeds: RSS, Atom, JSON

RSS and the indie web are having a comeback. I’ve been reading RSS on Feeeed and Feedbin lately, so proper feeds are essential to willow.camp.

My pet peeve with “follow me on RSS” links is that most of the time, you end up downloading a file when you click on the link. That’s not helpful.

I drew inspiration from Subscribe Openly and created a helpful “Subscribe” page with clipboard helpers to copy the feed of your choice easily. Check it out: enumerator.dev/subscribe.

API and CLI

willow.camp comes with an API and CLI tool for managing content. I’ve used the CLI to import and format my content from Ghost.

The CLI converts most of the HTML from Ghost correctly, but leaves some extraneous markdown that requires manual cleanup.

Todo list

I have lots of things I still want to do on willow.camp. Here are a few things I have in mind:

  • Write an Obsidian extension. I got Claude, Copilot, and Zed to build the CLI in Ruby. It does the job. However, what I’d like is a full-featured Obsidian extension that allows me to write, edit, publish, and sync my blog directly within Obsidian.
  • Re-implement syntax highlighting in code blocks. I think I messed up code syntax highlighting somewhere along the way. I want to fix this.
  • Fix the CLI tool’s gem publishing. Something borked here and I haven’t had a chance to fix it. If you want to use the latest CLI, you can build it from the repo for now.
  • Add a manual “light” and “dark” toggle to toggle between a user’s preferred schemes. This wouldn’t use prefers-color-scheme but would still allow light and dark mode.
  • Disallow a light scheme to be paired with Dracula because Dracula lives in the dark.

New Features

  • I love RSS, and I mostly love most RSS readers. But like a blogging platform, I’ve had trouble finding one that fits me. I want to build an RSS reader functionality into willow.camp so you can read RSS in the same place you publish.
  • Bookmarks! I love everything about raindrop.io, but I also like the challenge of building my own. So why not make willow.camp the super app of the indie web and add bookmarks? I’m joking here. I’d create a bookmarking feature for myself and hope that others will like it too.
  • Maybe introduce distribution lists? I liked that people could get my posts by email on Ghost. I didn’t want to see the open rates and graphs. I might re-introduce email distribution with a more straightforward UI.

Thoughts on Stimulus, Hotwire, and Turbo

Wow. I love this. Honestly, it makes me love developing web apps again. There is no heavy complexity between the frontend and backend. You don’t need to shift mindsets to start writing in a frontend framework.

I didn’t expect to enjoy these features this much. However, I’m back to writing HTML that maps directly to what I see in the browser, and a few extra bits of syntax in data-attribute fields make it a snappy and dynamic UI.

willow.camp is Free

willow.camp is free to use. If people find it useful, I might introduce a paid tier to cover hosting costs.

If you want a willow.camp blog, you can sign up and try it out.