In my previous post I wrote about how Mella CMS came together in six days. It ended with a list of plans: a shop, CSS effects, bug fixes and a security test. Four days have passed since then, 59 commits were added, and there are now 90 in total. The site is public, so every change goes straight in front of real visitors. Here is what happened in between and how much of the plan is already done.
Monday evening (19 October): pasted Markdown
The day ended with a small convenience. When Markdown text is pasted into the rich editor, the editor now turns it straight into properly formatted content. No more fixing asterisks and hash signs by hand.
Tuesday morning (20 October): spam and the blog
The day started with protection for the contact form. The form now asks the browser to solve a small proof-of-work puzzle and checks the email domain. A person never notices it, but for a bot it is expensive. I used a solution that does not need crypto.subtle, so the form also works in simpler environments. Public files also got a version number, so an old cache cannot hide new code.
At 10:14 I added a blog checkbox to the menu. Half an hour later a bigger piece was ready: blog comments with replies, and star ratings (over 1,100 lines added). The same day I replaced the browser's confirm() windows with the admin's own dialogs, so that everything looks consistent.
Tuesday afternoon: the shop and release packages
The biggest commit of the day was the optional e-shop (64 files, over 4,800 lines added). It has products, a cart, Stripe and PayPal payments, orders and digital downloads. The shop is switched on in Settings and protected by an activation code. The code is an Ed25519 signature whose private key only I hold.
To be honest, so far I have only tested payments against local Stripe and PayPal simulators, not their real sandbox environments. That is the next step before anyone moves real money.
Right after the shop I started working on making the CMS something I can hand to others:
customer release packages: a Docker image and a plain Node package with a minified server
a setup script that checks the Node version, installs dependencies and writes
.envready-made examples for Apache, nginx, Caddy, pm2, systemd and backups
a server bug: when the port was already in use, the server printed "running" and exited with code 0. Now it reports an error
the media library now also accepts
tar.gz,tgzandgzfiles
Wednesday morning (21 October): design effects
On Wednesday morning I made five commits in three hours, adding a new visual layer to the builder. Every block style can now have:
background gradients
filters and blend modes
hover effects
animations
The last commit updated the user guide, so the new effects are described and the screenshot scripts keep working.
Wednesday noon: a code audit
When a feature is done, the temptation is to move straight on to the next one. This time I asked Claude to first audit the whole codebase and look for leftover clutter. We checked unused exports, dependencies, translation keys, CSS classes, API endpoints, database tables, commented-out code and forgotten console.log calls.
The good news: there was little clutter. Every dependency is in use, every database table is in use, and even the API endpoints that looked unused at first glance are actually called by some page. There were still small leftovers:
five dead functions, constants and types that no other file used, plus one unused import and parameter
nine unused translation lines, left over from texts reworded during the shop work
15
eslint-disablecomments in a project that has no eslint at all. They looked like careful code, but were really meaningless noiseone unused icon file and an outdated file list in the README
All of it went away in one small cleanup, and afterwards both the type check and the build pass. I also made sure that no is-* or drop-* CSS class had become unused. Such classes are assembled dynamically in the code, so a simple text search would have wrongly declared them unused.
The biggest "clutter" was not in the code but in the history. The repo's .git folder had grown to 106 MB, and the cause was two user guide PDFs, about 9 MB each. Every update added a new version to the history, and eight or more had piled up. The PDFs are now out of version control and can be rebuilt with a script when needed. I did not rewrite the existing history, because that would change every commit ID. So the old size stays, but new PDFs no longer make it grow.
The audit also exposed a gap: the project has no automated tests. So far I have checked everything with the type check, the build and trying things by hand. Frankly, that is the biggest risk as the code keeps growing.
Wednesday evening (21 October): updates and releases
Once other people have installed the CMS, new versions have to reach them. That is what I worked on in the evening.
First I made the setup script POSIX-compatible, so it works on servers without /bin/bash. Then I made a decision that undid the previous day: I removed Docker. The Dockerfile, compose files, startup script and image release script went away, leaving the plain Node installation.
After that the update system was ready:
the admin checks once a day whether a newer version exists and shows a notice to administrators (Settings → Updates)
update.shdownloads the package, verifies the Ed25519-signed manifest and the SHA-256 checksum, backs up the data and the old code, and only then replaces the codea script builds and signs the release package
Right after that I wrote release.sh, which does everything in one command: bumps the version, takes the release notes, builds the package, uploads it and tags git. That same evening it produced two releases (1.2.1 and 1.2.2). The user guide also started to appear as web pages (in English and Estonian), and the PDF is no longer produced. That settled the PDF problem from noon for good.
Thursday morning (22 October): one-click update and 1.3.0
The morning began with refreshing the user guide pictures (175 files), and then came the Update button. In the admin a new version can now be installed with one click. The button makes a backup, shows the steps, and puts the old version back if the new one does not start. For it to work, the server's .env needs UPDATE_RESTART_CMD (1.2.3).
A little later I updated the sanitize-html library to version 2.18.1. A crafted piece of HTML could make the old version run out of stack, so this was a security fix (1.2.4). The same release brought the download page at cms.mella.ee/releases/, with the newest version, its checksum, release notes, and install and update instructions.
At 10:17 the next round of design effects began. In an hour and a half I added:
animations: blocks come in one after another, plus easing, distance, replay and more presets
hover for the parts of a block (for example a card's picture), gradient text and a fixed background picture
ready-made looks for blocks
scroll-linked animations: drift, grow and fade
a site style that sets how one page gives way to the next
a new Marquee block that moves words or pictures sideways
All movement switches off for visitors who prefer less motion.
At 10:57 came the menu rework. The Menu block is now a list of links, where each link opens in the same tab, a new tab or a pop-up, and can hold a submenu. Old menus keep working. Soon after, 1.3.0 was ready.
In numbers
First commit: 14 October 2026
Latest commit: 22 October 2026, 11:17
Commits in total: 90 (+59 since the previous post)
Releases: 1.2.1, 1.2.2, 1.2.3, 1.2.4 and 1.3.0
Biggest change: the shop, 64 files
What I learned
Spam arrives as soon as the site is public. That is why the contact form protection had to be ready before, not after.
Installation is a feature. The script and the server examples took less time than the shop, but they make the CMS usable by someone other than me.
Honesty about testing. It is better to say that payments were tried with simulators than to give the impression that everything was verified in the real world.
The biggest leftover may not be code. The history of two PDFs took more space than all the source code. Generated files do not belong in version control.
A comment can lie.
eslint-disableshows care for a tool that the project does not have. Leftovers like these are only found by an audit.A release should be one command. If bumping the version, notes, build, upload and tag are manual, one of them will go undone some day.
An update needs a way back. A button that backs up and restores the old version if the new one fails to start is simply more comfortable than updating by hand.
In closing
Of the plan in the first post, the shop and the CSS effects are now done, and on top of them the update system, the download page and the menu rework. The code audit is done and the clutter cleared. What is left is bug fixing, a security test and trying out the shop payments in a real sandbox. The first automated test is added to the list.
As in the previous post: a large part of the code was written by an AI, following my precise instructions. Let's see how far we get together with Claude.

Comments
No comments yet. Be the first!
Leave a comment