<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
	<title>Blog</title>
	<link rel="alternate" type="text/html" href="https://benjlu.com/archives" />
	<link rel="self" type="application/atom+xml" href="https://benjlu.com/archives/feed/atom.xml" />
	<id>https://benjlu.com/archives/feed/atom.xml</id>
	<updated>2026-11-05T12:00:00-08:00</updated>
	<entry>
		<title>Let the Merge Make the Tag</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/11/05/let-the-merge-make-the-tag" />
		<id>https://benjlu.com/archives/2026/11/05/let-the-merge-make-the-tag</id>
		<updated>2026-11-05T12:00:00-08:00</updated>
		<published>2026-11-05T12:00:00-08:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>I’ve tagged a release before merging it more than once. The pull request looked ready, so I created the tag, wrote the notes, and then merged. The tag pointed at the commit on the release branch instead of the merge commit. Each time, I had to delete the tag and…</summary>
		<content type="html">&lt;p&gt;I’ve tagged a release before merging it more than once. The pull request looked ready, so I created the tag, wrote the notes, and then merged. The tag pointed at the commit on the release branch instead of the merge commit. Each time, I had to delete the tag and the release and make them again.&lt;/p&gt;
&lt;p&gt;The fix wasn’t to be more careful. It was to stop tagging by hand.&lt;/p&gt;
&lt;h2&gt;Make the merge the release step&lt;/h2&gt;
&lt;p&gt;My projects already use a release branch named after the version, such as &lt;code&gt;release/1.1.6&lt;/code&gt;. That branch only bumps the version number and moves the changelog notes under the new heading. So the merge of that pull request is the moment the release is ready, and a workflow can do the rest.&lt;/p&gt;
&lt;p&gt;In GitHub Actions, the trigger is a pull request that was closed and merged into the main development branch from a branch that starts with &lt;code&gt;release/&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;on:
  pull_request:
    types: [ closed ]
    branches: [ develop ]

jobs:
  release:
    if: github.event.pull_request.merged &amp;amp;&amp;amp; startsWith( github.head_ref, &apos;release/&apos; )
    runs-on: ubuntu-latest
    permissions:
      contents: write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The job reads the version from the branch name and creates the release on the merge commit, which is &lt;code&gt;github.event.pull_request.merge_commit_sha&lt;/code&gt;. Using &lt;code&gt;gh release create&lt;/code&gt; with &lt;code&gt;--target&lt;/code&gt; set to that commit creates the tag in the same step.&lt;/p&gt;
&lt;h2&gt;Check before you publish&lt;/h2&gt;
&lt;p&gt;A release that runs on its own should refuse to run when something doesn’t match. Mine stops if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the branch name isn’t a valid version&lt;/li&gt;
&lt;li&gt;the version in the code doesn’t match the branch&lt;/li&gt;
&lt;li&gt;a release with that tag already exists&lt;/li&gt;
&lt;li&gt;the changelog has no section for that version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The release notes come from that changelog section, so the notes on GitHub match what’s in the repository.&lt;/p&gt;
&lt;h2&gt;One catch with built files&lt;/h2&gt;
&lt;p&gt;For a theme, the release also needs a ZIP. I had a separate workflow that built it whenever a release was published. A release created with the workflow’s own &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; doesn’t trigger other workflows, so that ZIP never appeared. Building and attaching the ZIP in the same workflow that creates the release fixed it.&lt;/p&gt;
&lt;p&gt;Now a release is one action: merge the release pull request. The tag lands on the right commit every time, and I don’t have to remember the order.&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Keeping a Homelab Change Log You’ll Actually Use</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/11/04/keeping-a-homelab-change-log-you-will-actually-use" />
		<id>https://benjlu.com/archives/2026/11/04/keeping-a-homelab-change-log-you-will-actually-use</id>
		<updated>2026-11-04T12:00:00-08:00</updated>
		<published>2026-11-04T12:00:00-08:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>After an evening of changing network settings, it’s easy to remember the fix and forget the two changes that came before it. A short log makes it easier to reconstruct what happened when a service breaks days later.
Keep the entry small
Start with the date and the system you…</summary>
		<content type="html">&lt;p&gt;After an evening of changing network settings, it’s easy to remember the fix and forget the two changes that came before it. A short log makes it easier to reconstruct what happened when a service breaks days later.&lt;/p&gt;
&lt;h2&gt;Keep the entry small&lt;/h2&gt;
&lt;p&gt;Start with the date and the system you touched. Write what changed, how you verified it, and what you would reverse if it caused trouble. A sample entry might read like this.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Moved the test web app behind the reverse proxy. Confirmed the homepage and one static asset over HTTPS from a second network. The previous proxy configuration is saved with the project’s configuration history.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Include details that will help your future self, such as a config path or an affected port. Keep passwords and tokens out of the log. A plain Markdown file is enough. The value comes from writing the entry while the change is fresh and being able to find it when you need it.&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>ClassicPress Customizer: postMessage or Refresh?</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/11/03/classicpress-customizer-postmessage-vs-refresh" />
		<id>https://benjlu.com/archives/2026/11/03/classicpress-customizer-postmessage-vs-refresh</id>
		<updated>2026-11-03T12:00:00-08:00</updated>
		<published>2026-11-03T12:00:00-08:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>When I add a setting to a ClassicPress theme, I start by asking what has to change in the preview. A color picker feels better when you can see the color as you move it. A sidebar setting may change which template loads and what markup it produces. Those two…</summary>
		<content type="html">&lt;p&gt;When I add a setting to a ClassicPress theme, I start by asking what has to change in the preview. A color picker feels better when you can see the color as you move it. A sidebar setting may change which template loads and what markup it produces. Those two settings call for different preview behavior.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;th&gt;What happens in the preview&lt;/th&gt;
&lt;th&gt;When I’d use it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;refresh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The preview reloads and PHP renders the page with the pending settings.&lt;/td&gt;
&lt;td&gt;Template changes, switching sidebar layouts, or settings that change what PHP outputs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postMessage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The preview receives the pending value and JavaScript updates the page in place.&lt;/td&gt;
&lt;td&gt;Text, colors, and other changes that affect elements already in the page.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;I start with &lt;code&gt;refresh&lt;/code&gt; and use &lt;code&gt;postMessage&lt;/code&gt; where the immediate feedback is worth maintaining a small JavaScript handler.&lt;/strong&gt; Both let you preview a pending change before publishing it. The transport changes how the preview updates. It doesn’t change how you register, sanitize, or save the setting.&lt;/p&gt;
&lt;h2&gt;Use refresh when PHP owns the layout&lt;/h2&gt;
&lt;p&gt;Imagine a setting that switches between a sidebar and a full-width page. If the full-width template leaves out the sidebar entirely, switching back involves more than changing a CSS class. The sidebar markup and widgets have to be rendered again. I’d keep that setting on &lt;code&gt;refresh&lt;/code&gt;, which is the default. That way, the preview uses the same PHP templates as the live site.&lt;/p&gt;
&lt;p&gt;If reloading the entire preview is distracting and PHP can render the change as a self-contained section, consider &lt;strong&gt;selective refresh&lt;/strong&gt;. It can replace that section using server-rendered markup. It’s worth a look when you need PHP to stay in charge but only part of the page needs updating.&lt;/p&gt;
&lt;h2&gt;Use postMessage for a footer credit&lt;/h2&gt;
&lt;p&gt;Here’s a complete preview example for plain footer text. Register the setting and control in your theme’s &lt;code&gt;functions.php&lt;/code&gt;. Replace the example text domain with your theme’s own.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;add_action( &apos;customize_register&apos;, function ( $customize ) {
    $customize-&amp;gt;add_section( &apos;example_footer&apos;, array(
        &apos;title&apos; =&amp;gt; __( &apos;Footer&apos;, &apos;example-theme&apos; ),
    ) );

    $customize-&amp;gt;add_setting( &apos;example_footer_credit&apos;, array(
        &apos;default&apos;           =&amp;gt; &apos;&apos;,
        &apos;sanitize_callback&apos; =&amp;gt; &apos;sanitize_text_field&apos;,
        &apos;transport&apos;         =&amp;gt; &apos;postMessage&apos;,
    ) );

    $customize-&amp;gt;add_control( &apos;example_footer_credit&apos;, array(
        &apos;section&apos; =&amp;gt; &apos;example_footer&apos;,
        &apos;label&apos;   =&amp;gt; __( &apos;Footer credit&apos;, &apos;example-theme&apos; ),
        &apos;type&apos;    =&amp;gt; &apos;text&apos;,
    ) );
} );

add_action( &apos;customize_preview_init&apos;, function () {
    wp_enqueue_script(
        &apos;example-customizer-preview&apos;,
        get_theme_file_uri( &apos;assets/js/customizer-preview.js&apos; ),
        array( &apos;customize-preview&apos; ),
        wp_get_theme()-&amp;gt;get( &apos;Version&apos; ),
        true
    );
} );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Render the saved value in the footer template. Keep the element present when the credit is empty so the preview script can still find it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;span id=&amp;quot;example-footer-credit&amp;quot;&amp;gt;&amp;lt;?php
    echo esc_html( get_theme_mod( &apos;example_footer_credit&apos;, &apos;&apos; ) );
?&amp;gt;&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save this as &lt;code&gt;assets/js/customizer-preview.js&lt;/code&gt; in the theme:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;wp.customize(&apos;example_footer_credit&apos;, function (setting) {
    setting.bind(function (value) {
        var credit = document.getElementById(&apos;example-footer-credit&apos;);
        if (credit) {
            credit.textContent = value;
        }
    });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The JavaScript treats the value as text, just as the PHP template escapes it for HTML. In the Customizer, type a new credit and clear it again. Both should update without reloading the preview. Publish a normal credit and open the site outside the Customizer to check the PHP output. Finally, try an unsaved edit and close the Customizer without publishing. The live site should still show the saved credit.&lt;/p&gt;
&lt;p&gt;If the control changes but the preview stays still, check that the script loaded inside the preview frame and that the setting and element IDs match. Setting &lt;code&gt;postMessage&lt;/code&gt; stops the normal reload, so the handler has to do the visible work.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ClassicPress/ClassicPress/blob/develop/src/wp-includes/class-wp-customize-setting.php&quot;&gt;ClassicPress setting implementation&lt;/a&gt; · &lt;a href=&quot;https://developer.wordpress.org/themes/classic-themes/customize-api/tools-for-improved-user-experience/&quot;&gt;Customizer handbook on live preview and selective refresh&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Merriment, a ClassicPress Theme</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/11/02/merriment-classicpress-theme" />
		<id>https://benjlu.com/archives/2026/11/02/merriment-classicpress-theme</id>
		<updated>2026-11-02T12:00:00-08:00</updated>
		<published>2026-11-02T12:00:00-08:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>Merriment is the Christmas theme I built for ClassicPress. A snowy header, a holly background, and evergreen and red accents give it a holiday look as soon as you activate it.
You can still choose a left sidebar, a right sidebar, or a single column. I wanted the theme to…</summary>
		<content type="html">&lt;p&gt;Merriment is the Christmas theme I built for ClassicPress. A snowy header, a holly background, and evergreen and red accents give it a holiday look as soon as you activate it.&lt;/p&gt;
&lt;p&gt;You can still choose a left sidebar, a right sidebar, or a single column. I wanted the theme to work for a regular blog that needs a seasonal look, with space for the same kinds of posts and conversations it carries the rest of the year.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://luthemes.com/portfolio/merriment&quot;&gt;View Merriment in the Luthemes portfolio&lt;/a&gt;.&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Doing the Work No One Sees</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/11/01/doing-the-work-no-one-sees" />
		<id>https://benjlu.com/archives/2026/11/01/doing-the-work-no-one-sees</id>
		<updated>2026-11-01T12:00:00-08:00</updated>
		<published>2026-11-01T12:00:00-08:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="faith" label="Faith" />
		<summary>And whatsoever ye do, do it heartily, as to the Lord, and not unto men;
— Colossians 3:23 (KJV)

Some of the work that helps people most is easy to miss. After fixing a problem, I can take a few minutes to record which setting changed and how I tested…</summary>
		<content type="html">&lt;blockquote&gt;
&lt;p&gt;And whatsoever ye do, do it heartily, as to the Lord, and not unto men;&lt;/p&gt;
&lt;p&gt;— Colossians 3:23 (KJV)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Some of the work that helps people most is easy to miss. After fixing a problem, I can take a few minutes to record which setting changed and how I tested it. The next person who runs into the issue may never know who wrote those notes, but they’ll have somewhere to start.&lt;/p&gt;
&lt;p&gt;This verse gives me a reason to care about that last step. There’s satisfaction in leaving a task in a condition that helps someone else, even when the only visible result is a few lines in a shared document.&lt;/p&gt;
&lt;p&gt;I think the same applies to cleaning up after a meal or putting tools back where the next person can find them. These jobs are small enough to overlook. They’re also specific places where I can pay attention and do something with care.&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Introducing Novaris: An Early Look</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/10/31/introducing-novaris" />
		<id>https://benjlu.com/archives/2026/10/31/introducing-novaris</id>
		<updated>2026-10-31T12:00:00-07:00</updated>
		<published>2026-10-31T12:00:00-07:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>I’ve been working on a project called Novaris, a flat-file CMS and framework for building websites with PHP and Markdown. It’s still early in development, but I wanted to introduce it here because it’s already part of how I build this site.
My background is in making themes and working…</summary>
		<content type="html">&lt;p&gt;I’ve been working on a project called Novaris, a flat-file CMS and framework for building websites with PHP and Markdown. It’s still early in development, but I wanted to introduce it here because it’s already part of how I build this site.&lt;/p&gt;
&lt;p&gt;My background is in making themes and working with WordPress and ClassicPress. I enjoy that work, and it has given me plenty to think about when it comes to content, templates, and the steps between writing a post and publishing it. Novaris gives me a place to explore those ideas in a project of my own.&lt;/p&gt;
&lt;h2&gt;Starting with files&lt;/h2&gt;
&lt;p&gt;The part I keep coming back to is having the content in files I can open in an editor. A post has a title, a date, and its Markdown text together in one place. I can keep it in Git, look at what changed, and move it with the rest of the site.&lt;/p&gt;
&lt;p&gt;For a personal blog like this one, that feels like a comfortable way to work. I can write a few paragraphs, check the preview, and keep going. The templates handle how the post looks, while the content stays readable on its own.&lt;/p&gt;
&lt;h2&gt;Building it through this site&lt;/h2&gt;
&lt;p&gt;I’m using Novaris on benjlu.com, which gives me real content to work with as I develop it. A dated archive, a long tutorial, and a short poem all ask slightly different things of a theme. Having them here helps me see where the framework makes the work easier and where I still need to improve it.&lt;/p&gt;
&lt;p&gt;The static export is another part I’m working with. It lets me turn the site into generated files that I can deploy. That gives me a reason to check the whole path from a Markdown post to the page a visitor opens, including its links, images, and stylesheets.&lt;/p&gt;
&lt;h2&gt;There’s more to work through&lt;/h2&gt;
&lt;p&gt;Novaris is still taking shape. I’m refining the code, working through rough edges, and figuring out which parts need better documentation. Using it for my own sites helps, but there’s plenty to learn about how someone else would set up a project and work with it.&lt;/p&gt;
&lt;p&gt;For now, I want to share the process as I go. I’ll write about the decisions behind Novaris, the problems I run into, and the small improvements that make it easier to use. This introduction is a starting point for those posts, and a way to give a name to the project you’re already seeing here.&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Submitting a Theme to WordPress.org</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/10/30/submitting-a-theme-to-wordpress-org" />
		<id>https://benjlu.com/archives/2026/10/30/submitting-a-theme-to-wordpress-org</id>
		<updated>2026-10-30T12:00:00-07:00</updated>
		<published>2026-10-30T12:00:00-07:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>After volunteering as a WordPress theme developer and reviewer, I think the most useful thing you can do before submitting a theme is to install the ZIP yourself on a clean site. By that point, you know every shortcut you took to get the theme running locally. A clean install…</summary>
		<content type="html">&lt;p&gt;After volunteering as a WordPress theme developer and reviewer, I think the most useful thing you can do before submitting a theme is to install the ZIP yourself on a clean site. By that point, you know every shortcut you took to get the theme running locally. A clean install helps you see what someone else will get when they download it.&lt;/p&gt;
&lt;h2&gt;1. Read the requirements before packaging&lt;/h2&gt;
&lt;p&gt;Start with the &lt;a href=&quot;https://make.wordpress.org/themes/handbook/review/required/&quot;&gt;Theme Review Requirements&lt;/a&gt;. Check the required files and metadata for your theme type, then look through the fonts, icons, images, and libraries you ship. Keep a record of each bundled asset’s source and license. If you can’t confirm that you’re allowed to redistribute something under a compatible license, resolve that before you upload.&lt;/p&gt;
&lt;p&gt;Look through the features as well. If a feature creates content that people should keep when they switch themes, such as a custom post type, put that functionality in a plugin. The theme can still control how the content looks.&lt;/p&gt;
&lt;h2&gt;2. Test the theme with someone else’s content&lt;/h2&gt;
&lt;p&gt;Activate the theme on a clean WordPress install and import the &lt;a href=&quot;https://developer.wordpress.org/themes/classic-themes/releasing-your-theme/theme-unit-test/&quot;&gt;Theme Unit Test data&lt;/a&gt;. Browse a long post title, a post with no featured image, an archive page, and a page with nested comments if your theme displays them. Test the navigation at a narrow screen width and use the Tab key to check that you can see where focus goes.&lt;/p&gt;
&lt;p&gt;Run the &lt;a href=&quot;https://wordpress.org/plugins/theme-check/&quot;&gt;Theme Check plugin&lt;/a&gt; and investigate the results. Then go back to the pages a person will actually use. Theme Check can flag problems in the files, and a browser test can show you where a layout breaks or a menu is hard to use.&lt;/p&gt;
&lt;h2&gt;3. Build and test the ZIP&lt;/h2&gt;
&lt;p&gt;Put the installable files in a single theme folder. If your build process compiles CSS or JavaScript, make sure the compiled files are in the package and the theme points to those files. Leave local dependencies and development artifacts such as &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;.git&lt;/code&gt; out of the ZIP.&lt;/p&gt;
&lt;p&gt;For example, if &lt;code&gt;my-theme/&lt;/code&gt; is a clean release folder, create the archive from its parent directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zip -r my-theme.zip my-theme/
unzip -l my-theme.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check that &lt;code&gt;my-theme/style.css&lt;/code&gt; is in the ZIP and that the paths match what the theme expects. Then install that exact ZIP through &lt;strong&gt;Appearance → Themes → Add New → Upload Theme&lt;/strong&gt; on your test site. Activate it and repeat a few of your browser checks.&lt;/p&gt;
&lt;h2&gt;4. Upload and follow the review&lt;/h2&gt;
&lt;p&gt;Sign in to WordPress.org and submit your ZIP through the &lt;a href=&quot;https://wordpress.org/themes/upload/&quot;&gt;Theme Upload page&lt;/a&gt;. Address any errors the uploader reports. After a successful submission, follow the theme’s review ticket for feedback.&lt;/p&gt;
&lt;p&gt;If a reviewer reports an issue, try to reproduce it first. Make the fix, increment the &lt;code&gt;Version&lt;/code&gt; in &lt;code&gt;style.css&lt;/code&gt;, rebuild and test the ZIP, and upload the new version through the same page. Reply on the ticket with what changed and how you tested it. A short note like “Fixed the mobile menu focus order and retested with the Tab key” gives the reviewer a specific check to repeat.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.wordpress.org/themes/releasing-your-theme/submitting-your-theme-to-wordpress-org/&quot;&gt;WordPress Theme Handbook submission guide&lt;/a&gt; · &lt;a href=&quot;https://developer.wordpress.org/themes/releasing-your-theme/updating-your-theme/&quot;&gt;Theme update instructions&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Introducing Chassis: Shared Building Blocks for My Themes</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/10/29/introducing-chassis" />
		<id>https://benjlu.com/archives/2026/10/29/introducing-chassis</id>
		<updated>2026-10-29T12:00:00-07:00</updated>
		<published>2026-10-29T12:00:00-07:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>When I’m building a theme, there are parts I want to spend time designing and parts I want to have ready to work with. The header needs to fit its content. The menu needs to open on a phone. A sidebar needs to move below the article when the screen…</summary>
		<content type="html">&lt;p&gt;When I’m building a theme, there are parts I want to spend time designing and parts I want to have ready to work with. The header needs to fit its content. The menu needs to open on a phone. A sidebar needs to move below the article when the screen gets narrow. Those details come up across projects.&lt;/p&gt;
&lt;p&gt;Chassis brings that shared structure into one package for ClassicPress themes and Novaris sites. It provides compiled styles, JavaScript for navigation and code-copy buttons, and PHP helpers for common components. Each theme can set its colors, fonts, spacing, and other design choices in a &lt;code&gt;chassis.json&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;Giving a theme its own look&lt;/h2&gt;
&lt;p&gt;The package includes the built CSS and JavaScript. Registering the appropriate service provider makes those assets available to the project. A theme’s design settings become CSS custom properties, so changing the header color or the space around the content can be as simple as editing JSON.&lt;/p&gt;
&lt;p&gt;Here’s a small &lt;code&gt;chassis.json&lt;/code&gt; example, placed in the theme’s root folder:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &amp;quot;$schema&amp;quot;: &amp;quot;https://raw.githubusercontent.com/techkodeninja/chassis/develop/schemas/chassis.schema.json&amp;quot;,
    &amp;quot;color&amp;quot;: {
        &amp;quot;header&amp;quot;: {
            &amp;quot;background&amp;quot;: &amp;quot;#1d3557&amp;quot;,
            &amp;quot;text&amp;quot;: &amp;quot;#ffffff&amp;quot;
        },
        &amp;quot;global&amp;quot;: {
            &amp;quot;accent&amp;quot;: &amp;quot;#e63946&amp;quot;,
            &amp;quot;accent-text&amp;quot;: &amp;quot;#ffffff&amp;quot;
        }
    },
    &amp;quot;font&amp;quot;: {
        &amp;quot;heading&amp;quot;: &amp;quot;Georgia, serif&amp;quot;
    },
    &amp;quot;spacing&amp;quot;: {
        &amp;quot;content-padding&amp;quot;: &amp;quot;l&amp;quot;,
        &amp;quot;sidebar-gap&amp;quot;: &amp;quot;xl&amp;quot;
    },
    &amp;quot;design&amp;quot;: {
        &amp;quot;radius&amp;quot;: &amp;quot;0.5rem&amp;quot;
    },
    &amp;quot;menu&amp;quot;: {
        &amp;quot;toggle&amp;quot;: &amp;quot;bottom-left&amp;quot;,
        &amp;quot;hide-on-scroll&amp;quot;: true
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I only need to include the values the theme changes. The schema helps an editor suggest settings, and a child theme can override individual values from its parent. Chassis also supports custom properties in the theme’s own stylesheet, loaded after the package styles.&lt;/p&gt;
&lt;p&gt;The responsive breakpoints are fixed in the compiled package. The design file controls values such as colors, widths, and spacing that can be expressed as custom properties.&lt;/p&gt;
&lt;h2&gt;A mobile menu with room to adjust&lt;/h2&gt;
&lt;p&gt;The menu button sits at the bottom left on phones by default. A theme can move it to the bottom right or either side of the header through &lt;code&gt;menu.toggle&lt;/code&gt;. Settings under &lt;code&gt;menu.design&lt;/code&gt; control its background, text, border, and padding.&lt;/p&gt;
&lt;p&gt;While scrolling down, the button hides to give the page more room. It returns when you scroll up, reach the top or bottom, open the menu, or tab to the button. Setting &lt;code&gt;&amp;quot;hide-on-scroll&amp;quot;: false&lt;/code&gt; keeps it visible.&lt;/p&gt;
&lt;p&gt;The script also handles Escape, clicking outside the menu, and opening submenus on touch screens. Those interactions are useful to share because they need attention in every theme, regardless of its colors or typography.&lt;/p&gt;
&lt;h2&gt;Connecting ClassicPress and Novaris&lt;/h2&gt;
&lt;p&gt;For a ClassicPress theme built on Backdrop, registering the Chassis provider loads the main CSS, the ClassicPress stylesheet, the design settings, and the JavaScript. The platform stylesheet covers markup such as comments, captions, widgets, and gallery shortcodes.&lt;/p&gt;
&lt;p&gt;On a Novaris site, the provider copies the built assets to &lt;code&gt;public/assets/chassis&lt;/code&gt;. The theme prints them using &lt;code&gt;styles()&lt;/code&gt; in the head and &lt;code&gt;scripts()&lt;/code&gt; near the end of the page, with the Chassis styles before its own stylesheet. Novaris also gets button and badge template tags.&lt;/p&gt;
&lt;p&gt;The underlying SCSS is still organized in eight numbered layers. I can use its Sass tools in custom theme partials when I need a shared spacing value or breakpoint helper.&lt;/p&gt;
&lt;h2&gt;Still taking shape&lt;/h2&gt;
&lt;p&gt;Chassis is developing quickly. I’m working toward a setup where a theme can start with the shared structure and spend more of its code on the design itself. The repository includes a theme-building guide and examples of the configuration.&lt;/p&gt;
&lt;p&gt;There’s still verification to do. The current changelog notes that the Backdrop provider and ClassicPress stylesheet haven’t yet been tested on a live ClassicPress site, and the complete guide still needs an end-to-end walkthrough. I’ll keep refining those parts as I use the package in more projects.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/techkodeninja/chassis&quot;&gt;Explore Chassis on GitHub&lt;/a&gt; · &lt;a href=&quot;https://github.com/techkodeninja/chassis/blob/develop/docs/build-a-theme.md&quot;&gt;Build a theme with Chassis&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Build Often, Release When Ready</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/10/28/build-often-release-when-ready" />
		<id>https://benjlu.com/archives/2026/10/28/build-often-release-when-ready</id>
		<updated>2026-10-28T12:00:00-07:00</updated>
		<published>2026-10-28T12:00:00-07:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="technology" label="Technology" />
		<summary>A small stylesheet tweak can be worth committing immediately even if the rest of the redesign needs another afternoon. When every commit deploys, it’s hard to save progress without also publishing it.
Give builds and releases different triggers
A push to a source branch can run the export and check…</summary>
		<content type="html">&lt;p&gt;A small stylesheet tweak can be worth committing immediately even if the rest of the redesign needs another afternoon. When every commit deploys, it’s hard to save progress without also publishing it.&lt;/p&gt;
&lt;h2&gt;Give builds and releases different triggers&lt;/h2&gt;
&lt;p&gt;A push to a source branch can run the export and check that key pages and assets exist. The result tells you whether the project is ready to ship. A separate release action can publish when the whole set of changes is ready.&lt;/p&gt;
&lt;p&gt;For a static site, the release record can be as simple as the source commit and the generated output it produced. If a problem appears later, that gives you a starting point for comparison. It also helps distinguish a successful publish to Git from a successful rollout at the hosting provider.&lt;/p&gt;
&lt;p&gt;GitHub Actions supports starting a release workflow manually with &lt;code&gt;workflow_dispatch&lt;/code&gt;. That gives a solo maintainer a straightforward way to choose when to go live. &lt;a href=&quot;https://docs.github.com/en/actions/how-tos/manage-workflow-runs&quot;&gt;GitHub workflow run documentation&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
	<entry>
		<title>Starting Again After a Rough Week</title>
		<link rel="alternate" type="text/html" href="https://benjlu.com/archives/2026/10/25/starting-again-after-a-rough-week" />
		<id>https://benjlu.com/archives/2026/10/25/starting-again-after-a-rough-week</id>
		<updated>2026-10-25T12:00:00-07:00</updated>
		<published>2026-10-25T12:00:00-07:00</published>
		<author><name>Benjamin Lu</name></author>
		<category term="faith" label="Faith" />
		<summary>It is of the LORD’s mercies that we are not consumed, because his compassions fail not. They are new every morning: great is thy faithfulness.
— Lamentations 3:22–23 (KJV)

A rough week can make a small setback feel permanent. A plan to make time for prayer or quiet can disappear…</summary>
		<content type="html">&lt;blockquote&gt;
&lt;p&gt;It is of the LORD’s mercies that we are not consumed, because his compassions fail not. They are new every morning: great is thy faithfulness.&lt;/p&gt;
&lt;p&gt;— Lamentations 3:22–23 (KJV)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A rough week can make a small setback feel permanent. A plan to make time for prayer or quiet can disappear after a few late nights, and by the weekend it’s easy to wonder whether it’s worth trying again.&lt;/p&gt;
&lt;p&gt;These words in Lamentations give me a different place to begin the morning. God’s mercy is already there when I wake up. I can open the Bible for a few minutes, say a short prayer, or make a call I’ve been putting off. Each is a real beginning.&lt;/p&gt;
&lt;p&gt;There may still be things from last week to put right. The promise of fresh mercy gives me room to deal with them one at a time. Today’s small act of attention is something I can do even after several days when I didn’t manage it.&lt;/p&gt;
</content>
	</entry>
</feed>
