Builder Receipt: Bonumark’s Theme Architecture

Bonumark Stream did not need more themes. It needed a theme system flexible enough to support genuinely different designs without letting presentation take control of the application.

Building Flexibility Without Giving Up Control

This development run started as architecture work and ended with a much better default theme, a more flexible Profile, and a performance problem that I did not consider finished until the actual PageSpeed results proved it was fixed.

The work covered a lot of ground, but the decisions stayed connected to one goal: Bonumark Stream needed a theming system that could support genuinely different presentations without letting themes take ownership of application behavior.

By the end of the work, I had also made a second decision. I did not want to maintain three first-party themes just because the architecture could support them. Bonumark Stream needed one default theme worth maintaining.

That theme would remain Midnight Ledger.

Finishing Declarative Theme Architecture

The first part of the work continued the Theme Architecture 2.0 project.

Profile and Stream Card composition had already established the direction. Themes could declare how core-owned components were arranged while Bonumark Stream continued to own the actual application.

I extended that model to the Site Header and Home.

The rule stayed strict throughout the work. Themes could control validated presentation and composition, but they could not own routes, database access, publishing behavior, permissions, authentication, navigation logic, forms, JavaScript application behavior, comments, likes, media processing, SEO behavior, or arbitrary executable code.

For the Site Header, I defined four legitimate core components:

  • site identity
  • primary navigation
  • menu toggle
  • stream count

I deliberately rejected the idea of exposing Midnight Ledger’s old “Live microblog” label as a universal Header capability. It was a theme presentation idea, not an application feature.

The same discipline applied to Home.

Instead of allowing a theme to reconstruct the publishing interface, I kept the composer atomic and defined five Home components:

  • notices
  • composer
  • pinned posts
  • feed
  • pagination

The feed continued to reuse the existing Stream Card renderer. I did not create a second post-rendering system just because Home became composable.

I also kept the empty state inside the feed component rather than inventing a theme expression or conditional system.

That mattered because I wanted the declarative API to stay small, understandable, and difficult to abuse.

Proving the Architecture Before Trusting It

I did not treat passing validation as enough proof.

Two temporary proof themes, Editorial and Split, were used to force the same Profile, Stream Card, Header, and Home data through materially different compositions.

That exposed whether the architecture was genuinely flexible or whether it was just Midnight Ledger with rearrangeable wrappers.

The themes proved the architecture.

They did not prove the designs were good.

That distinction became important.

I tested the proof themes on desktop and mobile and compared Profile layouts, Stream Cards, single-post pages, Header behavior, and Home composition. The underlying system held up. The visual results did not impress me.

That was useful information.

The architecture had succeeded, but the proof themes looked like architecture demonstrations instead of finished products.

I decided I did not want to maintain three themes simply because I had built the ability to do so.

Editorial and Split were removed from the bundled theme collection. Their declarative packages were retained as internal regression fixtures so the test suite could continue proving that unrelated layouts worked.

Midnight Ledger became the only bundled and default theme.

That simplified the product without throwing away the architectural proof.

Moving Midnight Ledger Onto the New System

I did not convert Midnight Ledger to Declarative Layouts all at once.

I first moved Profile and Stream Card onto Schema 1 because their existing structures mapped cleanly to the new component contracts.

I left Header and Home on the legacy renderer temporarily.

That was intentional.

Midnight Ledger’s Header still contained the “Live microblog” status and post count. Rather than polluting the general Header API with Midnight Ledger-specific concepts, I waited until I knew what I actually wanted the Header to become.

Once I decided those elements no longer deserved permanent space, they were removed from Midnight Ledger.

The Header could then become declarative using only legitimate core capabilities: site identity, navigation, and the Menu control.

Home followed later.

I kept its composition simple:

publish first, then read the Stream.

There was no reason to turn the default microblog theme into a dashboard just because the layout engine could support more elaborate arrangements.

By v0.5.114, Midnight Ledger was using Declarative Layouts for all four supported surfaces:

  • Profile
  • Stream Card
  • Site Header
  • Home

Legacy theme compatibility remained in core for other themes.

The Architecture Passed. The Theme Still Needed Work.

Once the architecture was finished, I stopped treating Midnight Ledger as a compatibility reference and started judging it as the actual default product.

The first screenshots made the weaknesses obvious.

There were too many boxes inside boxes. The visual hierarchy was weak. Desktop space was not being used well. Link previews became huge on mobile. The Comments interface looked disconnected from the post. The Header took up too much space. The Profile behaved like a long stack of fields instead of an identity page.

I worked through those problems using real screenshots instead of guessing from CSS.

The Profile became the strongest example of the redesign.

The cover image and identity area were visually connected. About became the primary narrative section. Now, Interests, and Links became supporting information rather than equal-weight blocks.

As more Profile content was added, another problem appeared.

Featured items and Photos continued down the left side after the supporting right rail ended, creating a large empty area on desktop.

I changed the layout again.

The upper section became a content-resilient two-column grid:

About on the left. Supporting information on the right.

Featured and Photos then moved below both columns and expanded across the full Profile canvas.

This was not designed around the exact amount of content on my Profile.

The requirement was that someone should be able to add more links, write a longer Now, add more interests, or have different amounts of Profile content without breaking the composition.

Featured waits until whichever upper column is taller has finished.

Photos also adapt to the amount of content. Desktop can use one, two, three, or four columns depending on the number of images. Mobile retains a predictable two-column gallery.

That made the Profile more useful as a default theme instead of merely making my Profile look good.

Fixing the Stream Without Destroying Its Focus

The Stream needed a different kind of work.

I wanted the actual posts to dominate instead of their containers.

I reduced visual chrome, softened borders and shadows, tightened radii, cleaned up the composer, and reduced the prominence of Likes, Comments, and Post Options.

Mobile link previews were one of the biggest problems.

A link preview image could become the largest thing inside a post and completely disrupt the Stream.

I changed mobile previews to compact image-and-text cards. The preview became supporting content again instead of becoming the post.

The single-post Comments area also changed.

Instead of a large Comments card containing another form card, the discussion now flows naturally underneath the post with a divider, heading, textarea, and Post Comment button.

The behavior stayed core-owned. The presentation became much calmer.

Catching a Proportion Problem Late

One of the last visual problems was subtle.

The Header looked right on the Profile but oversized above the Stream.

At first it sounded like a consistency problem. If the Header changed widths on different pages, would the site feel inconsistent?

Looking at the screenshots more carefully showed that the opposite was true.

The Profile used a broad identity canvas while the Stream used a narrower reading canvas. The Header matched the Profile but ignored the Stream.

The solution was not random per-page sizing.

I defined two deliberate Midnight Ledger canvases.

The identity canvas is used for Profile.

The reading canvas is used for Home, individual Stream posts, archives, and search.

The Header uses the same design everywhere but aligns itself with the content surface underneath it.

That made the site more consistent because the edges now follow a predictable system.

Responsive Testing Kept Finding Things Code Checks Could Not

Several issues only became obvious after looking at real mobile screenshots.

At intermediate widths, the Header title wrapped into two enormous lines even when there was enough room for one line.

Single-post typography also became oversized at that width.

The comment textarea remained much taller than expected because its HTML row count could produce a natural height larger than the CSS minimum.

Those problems were corrected in a responsive polish pass.

The Header now scales appropriately through common phone and small-tablet widths. Single-post typography remains more prominent than feed typography without becoming article-sized. The comment field uses a controlled mobile height while remaining resizable.

These were small changes, but they are exactly why I do not consider a responsive design finished because desktop looks good.

The Profile Performance Problem

Once the Profile design was where I wanted it, I ran PageSpeed Insights.

The Home and Stream results were already strong, so I deliberately left them alone.

The Profile was different.

Its initial performance scores were roughly:

  • 67 desktop
  • 74 mobile

Accessibility, Best Practices, and SEO were already strong.

The Profile had become image-rich. It included a cover, avatar, Featured content, and multiple Profile photos.

The PageSpeed report pointed toward image delivery.

I did not remove images or simplify the design just to improve a score.

I fixed the delivery.

Bonumark Stream was already generating some Profile derivatives, but the cover was not properly exposing those candidates through responsive markup. Profile gallery candidates were also too coarse for the actual image slots.

I updated the Profile media path so the browser could choose properly sized cover and gallery images.

The cover received responsive candidates and remained eager with high fetch priority because it was the likely Largest Contentful Paint element.

Gallery images received smaller candidates and remained lazy-loaded.

After that pass, PageSpeed improved to approximately:

  • 97 desktop
  • 82 mobile

That confirmed the diagnosis.

The layout was not the problem.

Image delivery was.

Getting the Profile to 100 Without Touching the Stream

I could have stopped at 97 and 82, but I wanted the default Profile experience to be as strong as I could reasonably make it.

The next requirement was strict: keep working on the Profile without getting wild and touching the Stream media system that was already performing well.

I added modern Profile-only image delivery.

When the server supports it, Bonumark Stream now creates WebP derivatives for Profile covers and gallery photos. The existing responsive JPEG or PNG path remains as fallback.

The cover also receives a responsive preload while retaining eager loading and high fetch priority.

Gallery photos remain lazy and receive low fetch priority.

Existing Profile media can generate missing derivatives as needed, while new uploads create them through the optimized path.

The result was the final PageSpeed test:

Desktop

  • Performance: 100
  • Accessibility: 100
  • Best Practices: 100
  • SEO: 100
  • FCP: 0.2 seconds
  • LCP: 0.3 seconds
  • Total Blocking Time: 0ms
  • CLS: 0.043

Mobile

  • Performance: 100
  • Accessibility: 100
  • Best Practices: 100
  • SEO: 100
  • FCP: 0.9 seconds
  • LCP: 1.2 seconds
  • Total Blocking Time: 0ms
  • CLS: 0

That was the result I wanted.

The Profile kept the cover, photos, content, and design. I did not have to gut the page to make it fast.

How I Verified the Work

Throughout the development run, I used more than one type of test.

Every package went through the Bonumark smoke suite.

I repeatedly checked PHP syntax, JavaScript syntax, JSON parsing, CSS integrity, release-manifest checksums, ZIP integrity, and the delivered ZIP after extraction.

When a pass was supposed to be presentation-only, I compared core files against the previous version to verify that renderers, templates, or layout contracts had not quietly changed.

When legacy behavior needed to remain identical, I compared normalized output against the previous package.

The declarative proof themes were used to test radically different layouts against the same core data.

Real desktop and mobile screenshots were used to test the things automated checks could not judge: hierarchy, proportion, overflow, density, responsive transitions, and whether the design actually looked good.

I also tested logged-out behavior. That exposed a gap beneath the Header before the first post. After checking the rendering path, I confirmed it was intentional site-shell spacing rather than an empty composer wrapper.

Not every visual difference needed fixing.

That distinction matters too.

What This Work Proved

The biggest result is not that Bonumark Stream can move components around.

It is that the theme boundary is now clearer.

Core owns the application.

Themes own presentation.

Midnight Ledger is now using the same declarative system that third-party themes can use, rather than remaining a special hard-coded exception.

At the same time, Bonumark Stream only bundles one theme, which means I am not creating unnecessary maintenance work for myself just to demonstrate flexibility.

The temporary themes proved the API.

Midnight Ledger proves the product.

The Profile also proved something important about performance work. A rich page does not automatically need to become a stripped-down page to become fast. The better answer was to find out what the browser was actually downloading and fix that part of the system.

What I Learned

The biggest lesson from this run is that architecture proof and product quality are different tests.

Editorial and Split proved that the declarative architecture worked. They still looked bad.

That was not a contradiction.

It was useful information.

Another lesson was to keep optimization scoped to the problem being measured. When the Stream was already performing well, I did not need to redesign its media system just because the Profile had a problem.

And real screenshots continued to matter.

Automated tests could prove that the correct component rendered. They could not tell me the Header looked oversized above a narrow Stream, that a mobile preview dominated the post, or that the bottom half of the Profile became visually unbalanced once more content was added.

Those problems required judgment.

By the end of the development run, Bonumark Stream had reached v0.5.120 locally, Midnight Ledger was the single bundled default theme, all four major surfaces were using Declarative Layouts, the Profile had become content-resilient, and its final PageSpeed tests reached 100 across Performance, Accessibility, Best Practices, and SEO on both desktop and mobile.

That is where I am stopping this work.

Not because there is nothing else that could ever be changed, but because the requirements I set for this development run have been met.


New Here?

Read Next:


Get the Work
Articles on discipline, recovery, identity, and ownership. Delivered when published.