Skip to content

Technology

Versioning Documentation with ProperDocs and mike

If you're using ProperDocs, versioned publishing should be straightforward. Build the docs for a release, publish them under a stable version label, and leave older versions alone. Until mike v2.2.0, that simple workflow still carried a nagging question: did the rest of the tooling really understand ProperDocs, or was it still assuming MkDocs under the hood?

mike publishes each supported release as its own directory tree on a deployment branch, usually gh-pages. Old versions stay put even when today's main branch moves on. In the mike v2.2.0 release, that process got simpler for ProperDocs users: when the properdocs package is installed, mike runs properdocs instead of mkdocs. No separate workflow branch. No wrapper script. No extra switch to remember.

The release notes include:

Add support for ProperDocs.

In practice, that means mike now checks for the properdocs package first. When it's present, mike uses the properdocs command and includes properdocs.yml and properdocs.yaml in its default configuration file search order.

That is exactly the kind of compatibility work you want in a documentation pipeline. Install the generator you actually use, install mike, and let tool detection do the boring part correctly.

ProperDocs exists because some teams need the MkDocs 1.x operating model to keep working. They have plugins, theme overrides, snippets, macros, and workflow assumptions built around that ecosystem. Versioned publishing shouldn't force those teams into a second set of build logic.

Before this change, the friction wasn't that versioned docs were impossible. The friction was that ProperDocs users had to think too much about whether the surrounding tooling still assumed MkDocs. That extra uncertainty is how CI jobs grow unnecessary conditionals and side paths.

With mike v2.2.0, the command flow is really simple:

python -m pip install "properdocs" "mike>=2.2.0"
mike deploy 1.0 latest --push

PowerShell Command Naming: Approved Verbs, Clear Nouns, and Better APIs

PowerShell PowerShell

The difference between a PowerShell command that feels native and one that feels bolted on is often not the implementation. It's the name. In PowerShell, names are part of the interface contract: they drive discoverability, shape user expectations, and determine whether your module behaves like a first-class citizen or like a private script someone published by accident.

Official References

Start with the official documentation for approved verbs, Get-Verb, and the PSScriptAnalyzer rule UseApprovedVerbs.

If you maintain PowerShell modules long enough, you start to see the same mistakes over and over:

  • Functions named with verbs PowerShell doesn't recognize
  • Plural nouns where PowerShell expects singular object names
  • Verb choices that imply one behavior while the function performs another
  • "Do everything" verbs like Invoke used as a substitute for design
  • State-changing commands that don't support -WhatIf and -Confirm

This post is about avoiding those mistakes and building command names that feel idiomatic, predictable, and maintainable.

A bad command name spreads farther than you might expect. It shows up in documentation, examples, tab completion, issue reports, code review comments, CI logs, and muscle memory. Once users learn the wrong name, you either carry it forward forever or you break them later when you fix it.

PowerShell isn't just a shell. It's a command discovery environment. Users don't need to remember your entire module if your names fit the ecosystem. They can find commands through patterns:

Get-Command -Verb Get
Get-Command -Module My.Module
Get-Command -Noun VcfCluster
Get-Help Get-VcfCluster -Full
Get-Verb

That only works well when command names follow the conventions the engine, the help system, and the user all expect.

The best PowerShell command names are easy to guess. If I know your module has a cluster object, I should be able to guess that retrieval is probably Get-Thing, creation is probably New-Thing, mutation is probably Set-Thing, deletion is probably Remove-Thing, and validation is probably Test-Thing. If your module breaks that expectation, you increase the amount of documentation the user must read before they can do anything useful.

Maintainer Burnout in Open Source

Most of the internet runs on digital duct tape: small libraries, CLI tools, and frameworks held together by maintainers who never billed you for the install. Your CI pipeline, your container base image, and your app's transitive dependencies all assume someone will answer the issue, cut the release, and patch the CVE on a timeline that looks suspiciously like "when they have a free evening."

Maintainer burnout is what you get when that assumption keeps landing. It's not a bad week or a need for better time management. You still care about the project. You still know what a good fix looks like. You just can't get there anymore without paying a cost you didn't use to pay. The code might take twenty minutes. The replies take the rest of your life.

Why Maintainer Burnout Happens

The public story of open source is commits and stars. The private story is triage, judgment calls, release notes, duplicate issues, security advisories, and the careful tone you need when you close a thread in public. Reply-shaped debt piles up in that gap. That's where maintainers break.

Volume Without Staff

Growth rarely brings reviewers, security rotation, or funded operations. It brings more issues, more pull requests, more "quick questions," and more combinations you'll never reproduce on your laptop. A star is a bookmark for a user. For a maintainer, it's a rough guess at how many strangers will show up in your inbox next quarter.

Entitlement as Urgency

Many users interact with free software as if it were a paid product: instant fixes, roadmap influence, enterprise response times, and frustration when you say "not yet." You inherit on-call dread without an on-call team. Every open thread reads like it's due now. Only you know which ones can wait until Saturday and which ones turn into someone else's emergency if you don't move tonight.

Guides like Please Format Your Code Blocks and Writing Practical Contribution Guidelines exist because unclear intake isn't rudeness. It's unpaid labor you pay before the real work starts.

Value Created, Compensation Delayed (or Never)

Industry products ship on volunteer maintenance. Sponsorship logos are easier to obtain than guaranteed hours. Maintainers run a second shift after paid work, doing release engineering and community moderation for strangers while their own sleep debt compounds.

The industry takes a lot upstream and sends back gratitude, sometimes money, almost never enough hands on the work.

Isolation and Identity

Solo maintainers carry product, support, security, and governance in one GitHub avatar. When a co-maintainer changes jobs and stops reviewing, the bus factor becomes you. For many people, the project is also résumé, community, and the thing they're known for. Stepping back can feel like disappearing. Staying in can feel like you're drowning. Burnout sometimes looks like silence while you still watch the notification count, unable to reply and unable to walk away.

Using @ Imports in CLAUDE.md

If you already keep agent policy in AGENTS.md, Cursor rules, GitHub Copilot instruction paths, or a long README.md, you usually don't want a second copy that drifts out of sync just for Claude Code.

Claude Code loads a project's CLAUDE.md every session. @path/to/file lines in that file can point at other files. At session start, Claude Code resolves those paths relative to the CLAUDE.md that contains them and includes their contents with the rest of the file.

This provides you a bridge to resuse agent context: One body of markdown on disk, while each integration still uses its own documented entrypoint (CLAUDE.md here, Cursor rules there, Copilot's matrix elsewhere).

Ansible Navigator: A Practical Deep Dive

Ansible

An Ansible playbook, or more specifically,ansible-playbook, runs from whatever Python, collections and system packages happen to be on the control node. That works when one person owns the environment or when a bastion is already pinned. It's a weak team contract: versions drift across laptops, CI runners, and shared hosts.

Ansible Navigator adds execution environments (container images for the control plane), a TUI for inspecting runs, and CLI behavior that lines up with AWX and Ansible Automation Platform (AAP). It runs and inspects the same playbooks and inventories when you want the runtime declared in an image tag instead of implied by the local machine.

This post covers installation, a first playbook, interactive vs stdout mode, custom images, configuration, and a CI-shaped workflow.

Using VS Code Remote Tunnels for Headless Remote Development

VS Code

The best development machine isn't always the one under your hands. Laptops are wonderful daily drivers, but they're also thermally constrained, battery-bound, and constantly moving between networks. For serious build and test work, especially on Go projects that need real Linux, Windows, and macOS coverage, I keep the compute somewhere stable and let my laptop act as the control plane.

With VS Code Remote Tunnels, you run the VS Code CLI on a remote machine, authenticate it with your GitHub account, and connect to that machine from VS Code or vscode.dev without opening inbound firewall ports, maintaining a VPN profile, or teaching dynamic DNS yet another way to disappoint you.

Remote development used to mean one of two things: SSH into a server and live in a terminal, or carry around a fragile stack of VPNs, jump boxes, port forwards, editor extensions, and local configuration. That worked, but it made the developer's laptop the place where every concern collided.

Modern remote development is a cleaner architectural split:

  • The laptop is the interface. It provides the screen, keyboard, editor, and whatever local ergonomics make you productive.
  • The remote node is the execution environment. It owns the CPU, memory, disk, operating system, SDKs, build cache, test dependencies, and long-running processes.
  • The identity provider brokers access. With Remote Tunnels, GitHub authentication gives you a familiar access path without requiring a public SSH endpoint.

It lets you treat development environments more like durable infrastructure and less like whatever happened to be installed on the laptop.

For my open source work, the benefits are immediate. The builds are fast, but fast is relative when you're compiling repeatedly, running integration tests, linting large module graphs, or validating cross-platform behavior against platform-specific dependencies. A headless machine with more cores, more memory, a warm module cache, and a stable network turns the edit-build-test loop into something predictable. The laptop stays cool. The battery lasts longer. The build cache stays where the builds happen.

A Mindful Habit for Maintaining Online Accounts

Free trials pile up. Side projects die. Shopping sites you used once still have your card on file. After a few years, you end up with a lot of accounts you don't remember creating and even less confidence in how they're secured.

Most online accounts don't disappear just because you stop using them. They can still hold your data, old passwords or recovery factors, and sometimes your payment details. Every forgotten login is another door you left unlocked.

  • Breach Exposure: forgotten accounts often still have weak or reused passwords, and many either don't support MFA or have it available but not enabled.
  • Privacy Leakage: abandoned accounts can keep personal data, billing details, and usage history long after you stop using the service.
  • Recovery Pain: if one gets hijacked, recovery can fail fast when the old email address, phone number, or other method is no longer yours.

Before deleting any account, confirm you no longer need access to purchased content, invoices, or exported data from that service.

Regular audits aren't exactly fun, but they keep your account footprint smaller and easier to defend.

I used to clean this up in occasional panic sessions. It never stuck. But what finally worked was a simple lifecycle: inventory, classify, decommission, and monitor.

Transitioning GitHub Pages to Deploy from Artifacts Instead of a Branch

For a long time, publishing this site was simple in the best possible way. ProperDocs built the HTML, Materialx handled the presentation, and a GitHub Actions workflow ran properdocs gh-deploy through task deploy. That command force-pushed the rendered site to a gh-pages branch, then GitHub's Pages backend quietly noticed the branch update and published it.

That model still worked, but the warning lights started blinking in the generated pages build and deployment job. GitHub was warning about Node.js 20 deprecation inside the Pages deployment path.

The noisy warning was only the symptom. The deeper problem was architectural: the deployment was still using a Git branch as an artifact transport.

The fix was to stop publishing a branch at all. The GitHub Action now builds the content, uploads the contents as GitHub Pages artifacts, and then actions/deploy-pages publishes it through GitHub's modern Pages deployment path.

Ansible Vault: A Practical Deep Dive

Ansible

Ansible runs need secrets at runtime: passwords, API tokens, private keys, TLS material, and cloud credentials. You don't have to keep those in Git. External stores, CI variables, and untracked local files are all valid patterns. Ansible Vault is for the case where you want encrypted variables and files versioned next to the playbooks, roles, and inventory that use them, with the vault password kept out of the repository.

This post walks through ansible-vault and the runtime flags that unlock encrypted content during a run: create and edit vaulted files, wire passwords from files or prompts, use vault IDs, and avoid the mistakes that still leak secrets after decryption.

Vault is built into Ansible. It's not a centralized secrets manager, and it doesn't replace runtime discipline around logs, registers, and task output.

Using a GitHub Discussions-First Approach Intake for Maintainers

I still spend time re-routing #1234: a question filed as a bug, a feature pitch that should've been an Ideas thread, an install problem with no version in the body. Discussion category forms fix what shows up in the forum once someone lands there. This post is about policy and wiring: where contributors go first, and what happens when they open New issue anyway.

Some maintainers want questions, ideas, and suspected bugs in GitHub Discussions, triaged in public, with Issues opened only after the work is real. That takes five pieces: chooser routing, a reserved issue template, discussion forms (see How to Write Effective GitHub Discussion Templates for YAML and the Ideas and Community Help examples), CONTRIBUTING.md, and a pinned rules thread.