Code, Content, and Career with Brian Hogan logo

Code, Content, and Career with Brian Hogan

Archives
Subscribe
Dec. 31, 2025, 4:18 p.m.

Issue 48 - The WFH Commute and Ensuring Markdown Consistency

You'll explore the importance of 'commutes' even when working from home, and how to use markdownlint for consistent Markdown structure.

Code, Content, and Career with Brian Hogan Code, Content, and Career with Brian Hogan

This issue marks four years of this newsletter. Thank you to all of the readers who open this newsletter and engage with me on various platforms about the content.

To close out the year, I'll expand on some advice I gave in Issue 38 around remote work and the importance of a commute to create better boundaries between work and home. Then you'll explore how to ensure consistent structure in your Markdown documents using markdownlint.

Working From Home? You Still Need to Commute.

Working from home has many benefits. One of the most important benefits people talk about is the fact that there's no commute. If you work a job that requires you to trek into an office, you're taking time out of your week to get to work. That's time you're not compensated for, and that's time you won't get back.

However, commutes have mental benefits that people who work from home often overlook. A commute gives you a transition. In the morning, your commute can help you mentally prepare for your workday. And at the end of the day, your commute can help you leave the stresses of work at the office.

Sociologist Ray Oldenburg described three types of places in our lives. Each serves a different psychological function:

  • First place: Home (private, personal, rest)
  • Second place: Work (productive, professional, structured)
  • Third place: Social gathering spots like cafes, gyms, churches, pubs (community, connection)

When you work from home, the first and second place collapse into one, which means the place where you're supposed to rest becomes the place where you produce. Your responsibilities in that place stack. You may be tempted to be "online" all the time because you never fully separate from work. You might jump straight from family time into work, or bring work stress back to your friends and family. Without clear boundaries, it's harder to mentally switch off and you get stuck in this in-between place.

Solve for this by creating your own commute so you can transition between the first and second space. Instead of rolling out of bed and jumping into your office chair, treat your work-from-home job like a real job. Have breakfast. Shower. Get ready for the day. Listen to a podcast or audiobook. Go for a short walk around your neighborhood. Do something to mentally and physically transition from home life to work life.

And at the end of the day, do the same. Don't just close the laptop and go into your evening. Do something to clear your head. Go for another walk while listening to a podcast, or do some meditation and deep breathing. One great end-of-day activity is for you to Journal about your day. Record your mood, your frustrations, and any accomplishments. This helps you de-stress, but it also gives you a nice record you can reflect on at the end of the year when it's time to do performance reviews.

The goal is to create a boundary between your workday and your home life. This is especially important if you live in a smaller apartment where your workstation might be in the same room where you live the rest of your life. If at all possible, don't work where you relax. If you're constrained for space and can't make it to a coworking space, at least set up a small desk so that you have a dedicated work zone.

When you don't create barriers between your two worlds, they'll bleed into each other. You'll bring your work stress into your personal life, and you'll be distracted by personal things while working. People working in offices get the benefit of a runway to ramp up and ramp down. You can, and should, incorporate that into your own day too.

Things To Explore

  • This massive blog post on using Claude Code is a great read if you're looking to understand how you might fit Claude Code into your workflow.
  • FreeCodeCamp has a Relational Databases certificate course now. I'll be evaluating this course soon because I'm always looking for good resources on this topic.

Lint Your Markdown with markdownlint

Markdown is flexible, but when many people contribute to the same documentation, you can end up with inconsistencies. One author uses asterisks for lists, while another uses dashes. Someone forgets blank lines around headings. Another person indents with tabs instead of spaces. The rendered output might look fine, but the source files become inconsistent and harder to maintain.

markdownlint checks Markdown files for structural and formatting issues. It catches things like inconsistent heading styles, missing blank lines, trailing whitespace, and improper list indentation.

markdownlint focuses on structure, not prose. It won't tell you that your sentences are too long or that you're using passive voice. To solve for those cases, use Vale instead. If you want to dig deeper into Vale, check out my book Write Better with Vale. Together, markdownlint and Vale give you comprehensive coverage: one handles the structure of your Markdown, the other handles the quality of your writing.

To explore how markdownlint works, you'll create a Markdown document with some issues, run markdownlint against the file, configure markdownlint to ignore certain rules, and then write a custom rule.

Set up a project

Create a directory for this project and switch to it:

$ mkdir markdown-lint-demo
$ cd markdown-lint-demo

Initialize a new npm project in this directory using the defaults:

$ npm init -y

Now install the markdownlint CLI tool as a project dependency:

$ npm install markdownlint-cli2

The markdownlint-cli2 package is the modern command-line interface for markdownlint. It lets you use a single .markdownlint-cli2.jsonc configuration file that handles rule settings, file ignores, and custom rules all in one place.

Now you need a file with some errors so you can lint it. Create a file called example.md with the following content:

# My Article
This paragraph has no blank line above it.
##  Installation

First, download the package. This line has a trailing tab.

#### Deep Heading

This heading skipped a level.

## Getting Started
Some content here.

This file has several structural problems. The first paragraph has no blank line separating it from the heading. The "Installation" heading has an extra space after the hashes. There's a trailing tab character on one line. The "Deep Heading" section skips from level 2 to level 4. And "Getting Started" has no blank line before it. This file would absolutely render, but it could cause problems and would be inconsistent.

Now run markdownlint against the sample file:

$ npx markdownlint-cli2 example.md

The output shows each issue found:

markdownlint-cli2 v0.20.0 (markdownlint v0.40.0)
Finding: example.md
Linting: 1 file(s)
Summary: 5 error(s)
example.md:1 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "# My Article"]
example.md:3:4 error MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "##  Installation"]
example.md:3 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "##  Installation"]
example.md:7 error MD001/heading-increment Heading levels should only increment by one level at a time [Expected: h3; Actual: h4]
example.md:11 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## Getting Started"]

Each line tells you the file, line number, rule ID, rule name, and a description of the problem. The rule IDs like MD019 and MD022 are useful when you want to ignore specific rules.

Configure markdownlint to ignore rules

You might not want to use every rule. Maybe your team allows skipped heading levels for stylistic reasons, or you don't care about trailing whitespace because your editor strips it when you save. You can turn rules off by using a configuration file.

Create a file called .markdownlint-cli2.jsonc in your project directory. Add the following code to the file to configure some rules:

{
  "config": {
    "default": true,
    "MD001": false,
    "MD009": false
  }
}

This configuration starts with all default rules enabled, then disables MD001, which enforces heading level increments, and MD009, which flags trailing whitespace.

Run markdownlint again:

$ npx markdownlint-cli2 example.md

The output now shows fewer errors:

markdownlint-cli2 v0.20.0 (markdownlint v0.40.0)
Finding: example.md
Linting: 1 file(s)
Summary: 4 error(s)
example.md:1 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "# My Article"]
example.md:3:4 error MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading [Context: "##  Installation"]
example.md:3 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "##  Installation"]
example.md:11 error MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## Getting Started"]

The heading increment and trailing whitespace violations are gone.

Automatically fix errors

Markdownlint can fix your files for you. Run markdownlint-cli2 with the --fix flag:

$ npx markdownlint-cli2 --fix example.md

The results now show that things are fixed:

markdownlint-cli2 v0.20.0 (markdownlint v0.40.0)
Finding: example.md
Linting: 1 file(s)
Summary: 0 error(s)

markdownlint can't automatically fix everything, but it can fix basic things like extra spaces, indentation issues, or missing blank lines around elements. This can be a huge time-saver overall.

Write a custom rule

The built-in rules cover common issues, but sometimes you need project-specific checks. For example, suppose your style guide requires every article to end with a "Conclusion" section. You won't find a built-in rule to enforce this, but you can write your own custom rule.

Create a file called custom-rules.js to hold the rule definition. Add the following code to define a rule that finds all level-2 headings, then checks if the last one is "## Conclusion":

module.exports = {
  names: ["require-conclusion"],
  description: "Documents must end with a ## Conclusion heading",
  tags: ["headings"],
  function: function rule(params, onError) {
    const lines = params.lines;

    // Find all h2 headings
    const h2Headings = [];
    lines.forEach((line, index) => {
      if (/^## /.test(line)) {
        h2Headings.push({ line: line, lineNumber: index + 1 });
      }
    });

    // Check if there are any H2 headings
    if (h2Headings.length === 0) {
      onError({
        lineNumber: lines.length,
        detail: "No ## headings found"
      });
      return;
    }

    // Check if the last h2 heading is "## Conclusion"
    const lastH2 = h2Headings[h2Headings.length - 1];
    if (!/^## Conclusion\s*$/.test(lastH2.line)) {
      onError({
        lineNumber: lastH2.lineNumber,
        detail: "Last ## heading must be '## Conclusion'",
        context: lastH2.line
      });
    }
  }
};

The rule exports an object with a few properties. The names array contains identifiers for the rule. The description explains what the rule checks. The tags array groups related rules together. The function property contains the actual check logic.

The function receives two arguments: params, which contains information about the document, and onError, a callback function you call when you find a violation. The params.lines array contains each line of the document as a string.

When the function is unable to find the "Conclusion" header, it executes the onError callback, sending back the line number, a message, and the text of the line. That's what markdownlint displays in its output.

To use the custom rule, pass it to markdownlint with the --rules flag:

$ npx markdownlint-cli2 --rules ./custom-rules.js example.md

The output now includes your custom rule violation:

markdownlint-cli2 v0.20.0 (markdownlint v0.40.0)
Finding: example.md
Linting: 1 file(s)
Summary: 1 error(s)
example.md:13 error require-conclusion Documents must end with a ## Conclusion heading [Last ## heading must be '## Conclusion'] [Context: "## Getting Started"]

markdownlint can't automatically fix this issue when you use the --fix flag. Add a ## Conclusion section to the end of your document to fix the issue manually.

You can also load custom rules through the configuration file instead of passing the --rules flag every time. Update your .markdownlint-cli2.jsonc:

{
  // Load custom rules from a local file
  "customRules": ["./custom-rules.js"],

  // Standard rule configuration
  "config": {
    "default": true,
    "MD001": false,
    "MD009": false
  }
}

Run markdownlint without specifying the rules flag:

npx markdownlint-cli2 example.md

Now markdownlint loads your custom rules along with the other configuration changes you made.

Ignoring files

You can run markdownlint against all files in the current directory and child directories, but then you might scan things you don't care about. To ignore specific files, add an ignores array to your configuration. For example, add the following to ignore the node_modules directory, the vendor/ folder, and the CHANGELOG.md file:

{
  "ignores": ["node_modules/", "vendor/", "CHANGELOG.md"],
  "config": {
    "default": true
  }
}

Now run the command against all Markdown files in the current directory and its children:

$ npx markdownlint-cli2 "**/*.md" 

This time the output shows the errors as well as the files that markdownlint ignored:

markdownlint-cli2 v0.20.0 (markdownlint v0.40.0)
Finding: **/*.md !node_modules/ !vendor/ !CHANGELOG.md
Linting: 1 file(s)
Summary: 1 error(s)
example.md:13 error require-conclusion Documents must end with a ## Conclusion heading [Last ## heading must be '## Conclusion'] [Context: "## Getting Started"]

With this, you can run the tool against all of your project's documentation without having to review things you don't need to be responsible for.

Integrating with your workflow

Now that you have markdownlint working, tie it into your workflow. The VS Code extension highlights issues as you write, so you get real-time feedback. You can also add markdownlint to your CI pipeline to catch issues before you publish. And you can combine markdownlint with Vale to cover both structure and prose as part of your publication flow.

Consistent documentation is easier to read, easier to maintain, and easier to convert to other formats. Set up the rules once, and let the tools do the enforcement for you.

Parting Thoughts

Before the next issue, try some of the following things:

  1. Add markdownlint to your blog. How consistent is your Markdown?
  2. If you work from home, design your commute. Try going for a brief walk around your neighborhood in the morning and journaling at the end of the day. Do this for the next month and see how you feel.

I'd love to talk with you about this issue on BlueSky, Mastodon, Twitter, or LinkedIn. Let's connect!

Please support this newsletter and my work by encouraging others to subscribe and by buying a friend a copy of Write Better with Vale, tmux 3, Exercises for Programmers, Small, Sharp Software Tools, or any of my other books.

You just read issue #48 of Code, Content, and Career with Brian Hogan. You can also browse the full archives of this newsletter.

Read more:

  • Mar 01, 2025

    Issue 38 - Effective Remote Work and Why Books Still Matter

    Tips on working remotely, and why books still matter in the age of LLMs.

    Read article →
  • Feb 28, 2023

    Issue 14 - Interviewing When You Like Your Job, Enforcing Style, and a Bore-ing Proxy Tool

    Hi friends. February draws to a close, and it’s been a busy month. A couple of weeks ago, my former team was laid off, and since then, I’ve been networking...

    Read article →
Share this email:
Share on Twitter Share on LinkedIn Share on Hacker News Share on Reddit Share via email Share on Mastodon Share on Bluesky
Twitter
LinkedIn
Mastodon
Bluesky
Powered by Buttondown, the easiest way to start and grow your newsletter.