﻿<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text" xml:lang="en">Blog</title>
  <link type="application/atom+xml" href="https://mfgames.com/blog/atom.xml" rel="self" />
  <link type="text/html" href="https://mfgames.com/blog/" rel="alternate" />
  <updated>2026-10-05T17:39:37Z</updated>
  <id>https://mfgames.com/blog/</id>
  <author>
    <name>D. Moonfire</name>
  </author>
  <rights>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</rights>
  <entry>
    <title>v2.0.0 - Changing Defaults (MfGames Project Setup Flake)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/09/21/changing-defaults/" />
    <updated>2026-09-21T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/09/21/changing-defaults/</id>
    <category term="mfgames-project-setup-flake" scheme="https://mfgames.com/categories/" label="MfGames Project Setup Flake" />
    <content type="html">&lt;p&gt;I like using
&lt;a href="//mfgames.com/mfgames-conventional-commit-rs/"&gt;mfgames-conventional-commit&lt;/a&gt; to
handle &lt;a href="https://semantic-release.org/intro/"&gt;semantic releases&lt;/a&gt;. However, a
recent change to one of the supporting crates of that program caused a problem
with building in pipelines, so I decided to make a breaking change to the flake,
but also used that as an opportunity to add a few things.&lt;/p&gt;
&lt;p&gt;The biggest one is &lt;code&gt;mfgames-conventional-commits&lt;/code&gt; is not enabled by default.
This means that it needs to be enabled as part of the flake:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;{
  description = &amp;quot;Description for the project&amp;quot;;

  inputs = {
    flake-parts.url = &amp;quot;github:hercules-ci/flake-parts&amp;quot;;
    nixpkgs.url = &amp;quot;github:NixOS/nixpkgs/nixos-unstable&amp;quot;;
    rust-flake.url = &amp;quot;github:juspay/rust-flake&amp;quot;;
    mfgames-project-setup.url = &amp;quot;git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git?ref=refs/tags/v1.4.2&amp;quot;;
  };

  outputs =
    inputs@{ flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } (
      { withSystem, flake-parts-lib, ... }:
      {
        imports = [
          inputs.mfgames-project-setup.flakeModules.default
        ];

        # Configure mfgames project settings.
        mfgames.project = {
          enable = true;
          mfgames-conventional-commit.enable = true;
        };
      })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should also speed up builds where the commits aren't required.&lt;/p&gt;
&lt;h2&gt;C# Changes&lt;/h2&gt;
&lt;p&gt;It looks like the change to Microsoft's C# guidelines are part of this release.
This uses the &lt;code&gt;.editorconfig&lt;/code&gt; settings from
&lt;a href="https://github.com/dotnet/runtime/blob/main/.editorconfig"&gt;Microsoft's Runtime team&lt;/a&gt;.
We were originally using the StyleCop from another Microsoft team, but this
appears to be the &amp;ldquo;winner&amp;rdquo; for more consistent formatting.&lt;/p&gt;
&lt;p&gt;It will take me a while to get used to &lt;code&gt;_variable&lt;/code&gt; instead of &lt;code&gt;this.variable&lt;/code&gt;,
but I think it will be good in the long run.&lt;/p&gt;
&lt;h2&gt;Additional Changes&lt;/h2&gt;
&lt;p&gt;Since I was editing the file, I added a few things that were on my issue list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updated the base packages to 26.05 &lt;code&gt;nixpkgs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Added formatting of just files with &lt;code&gt;just.format&lt;/code&gt; and the &lt;code&gt;treefmt&lt;/code&gt; settions.
This defaults to enable.&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;treefmt.enable&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;) to disable &lt;code&gt;treefmt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;just.format&lt;/code&gt; (default &lt;code&gt;true&lt;/code&gt;) to format Just files. If enabled,
&lt;code&gt;treefmt&lt;/code&gt; will handle that.&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;treefmt.extraFormatters&lt;/code&gt; to provide custom formatting rules because it
was annoying to add the shell formatter before I integrated it.&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;javascript.enable&lt;/code&gt; to add &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; to the
&lt;code&gt;prettier&lt;/code&gt; ignore list.&lt;/li&gt;
&lt;li&gt;Moved some JavaScript and TypeScript file extensions to be controlled by the
respective &lt;code&gt;format&lt;/code&gt; option (defaults to the &lt;code&gt;enable&lt;/code&gt; flag).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also found &lt;a href="https://github.com/nix-community/nixdoc"&gt;nixdoc&lt;/a&gt; after writing my
own implementation of documentation. So I changed it to use that because I'm all
for using other people's code to do something so I don't have to maintain it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Configuration Arguments for v0.4.0 (MfGames Conventional Commit)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/09/20/config/" />
    <updated>2026-09-20T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/09/20/config/</id>
    <category term="mfgames-conventional-commit" scheme="https://mfgames.com/categories/" label="MfGames Conventional Commit" />
    <content type="html">&lt;p&gt;We've been writing a number of CLI tools lately. When that happens, certain patterns start to become more obvious.&lt;/p&gt;
&lt;p&gt;One thing is creating tables for output, which we just extracted into &lt;a href="https://src.mfgames.com/mfgames-rs/mfgames-cli-table"&gt;mfgames-cil-table&lt;/a&gt; into this program. There isn't any changes from that, mostly because we were extracting something we've been copy/pasting for a year.&lt;/p&gt;
&lt;p&gt;The other change, however, is obvious. We've introduced the &lt;code&gt;--config&lt;/code&gt; option. Previously, the configuration for &lt;code&gt;mfgames-conventional-commit&lt;/code&gt; was hard-coded to be &lt;code&gt;//.git/mfgames-conventional-commit.yaml&lt;/code&gt;. But, most of our other programs use &lt;code&gt;--config&lt;/code&gt; to get rid of the hard-coding so we decided to do the same.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ mfgames-conventional-commit --config .config/dev-mfgames-conventional-commit.json  version
 package_name  last_version  flatten_version  flatten_changed  detail_version  detail_changed
 default       0.4.0         0.4.0            false            0.4.0           false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there is no &lt;code&gt;--config&lt;/code&gt;, then this continues to work as normal. However, if one or more &lt;code&gt;--config&lt;/code&gt; files are included on the path, those take priority.&lt;/p&gt;
&lt;h2&gt;Split Processing&lt;/h2&gt;
&lt;p&gt;This is one approach to handling a current limitation for this tool. At the moment, we only have one set of &amp;ldquo;update&amp;rdquo; commands in the configuration file. Those commands can be used to update versions inside assembly files or configurations. It can also be used to tag and push up releases, but there are times when one needs to update the versions before the build but do the tagging after the build.&lt;/p&gt;
&lt;p&gt;To get around that, the &lt;code&gt;--config&lt;/code&gt; can be used to have three files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;//.config/mfgames-conventional-commit.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;//.config/mfgames-conventional-commit-prepare.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;//.config/mfgames-conventional-commit-release.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first file contains the common rules for packages, how to find them, which ones exist. The second can be used for the actions that need to be done before the build such as updating version files.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ mfgames-conventional-commit --config .config/mfgames-conventional-commit-prepare.json update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the third file can be used only in pipelines or for the release process to perform the actual tagging and uploading of binaries.&lt;/p&gt;
&lt;p&gt;There might be a cleaner way to do this in the future, but at the moment, that's all we got.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Introducing mfgames-cli-table for Rust (mfgames-cli-table)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/09/18/introducing/" />
    <updated>2026-09-18T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/09/18/introducing/</id>
    <category term="mfgames-cli-table" scheme="https://mfgames.com/categories/" label="mfgames-cli-table" />
    <content type="html">&lt;p&gt;One of the guiding principles of our development is the &lt;a href="https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)"&gt;Rule of Three&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Three strikes and then you refactor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this case, a common pattern in our CLI's is generating tables. While this is relatively simple at the moment, it can quickly become complex. For example, we normally like our tables to be simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ just example-basic
 name   value
 alfa   1
 bravo  2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But there are times when we want to dump it to Markdown or JSON:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ just example-basic --markdown
| name  | value |
|-------|-------|
| alfa  | 1     |
| bravo | 2     |

$ just example-basic --json | jq
[
  {
    &amp;quot;name&amp;quot;: &amp;quot;alfa&amp;quot;,
    &amp;quot;value&amp;quot;: 1
  },
  {
    &amp;quot;name&amp;quot;: &amp;quot;bravo&amp;quot;,
    &amp;quot;value&amp;quot;: 2
  }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the moment, there isn't much to this beyond what is displayed but we've created the same libraries in different languages and purposes, so we know that this has the potential of being more featured as time progresses. And it would be better to make it central now so we don't have to reinvent the wheel later.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Writing out to YAML&lt;/li&gt;
&lt;li&gt;Adding/removing columns&lt;/li&gt;
&lt;li&gt;Sort the output based on columns&lt;/li&gt;
&lt;li&gt;Basic filtering&lt;/li&gt;
&lt;li&gt;Right-align numbers in displayed tables&lt;/li&gt;
&lt;li&gt;Dot-align decimals&lt;/li&gt;
&lt;li&gt;Sending the table output to a file&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>New Version - v0.5.1 (Leicmin)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/08/14/new-version-v0.5.1/" />
    <updated>2026-08-14T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/08/14/new-version-v0.5.1/</id>
    <category term="leicmin" scheme="https://mfgames.com/categories/" label="Leicmin" />
    <content type="html">&lt;p&gt;Well, it's been four months since we introduced &lt;a href="//mfgames.com/leicmin/"&gt;Leicmin&lt;/a&gt;. As far as we know, there is only one instance &amp;ldquo;in the wild&amp;rdquo; but that one instance has given us quite a list of issues, observations, and pain points.&lt;/p&gt;
&lt;h2&gt;Group Passwords&lt;/h2&gt;
&lt;p&gt;Probably the biggest one was how group passwords work. This is our attempt to handle providing accounts and passwords for static websites. Naturally, this is always a difficulty since we don't have a &lt;code&gt;/auth/me&lt;/code&gt; endpoint that some sites have (mostly because we have no clue how to set it up), and OpenID Connect (OIDC) requires a bit of dynamic code (not to mention having Leicmin act as a OIDC provider, which is on the future list).&lt;/p&gt;
&lt;p&gt;So that means we have an endpoint to generate &lt;a href="https://httpd.apache.org/docs/current/programs/htpasswd.html"&gt;htpasswd&lt;/a&gt; files directly which means the passwords are encrypted from the server down to the client, but it also means we have to decrypt the passwords in the server. This is in contrast to account passwords which are hashed with &lt;a href="https://en.wikipedia.org/wiki/Argon2"&gt;argon2&lt;/a&gt; and not recoverable.&lt;/p&gt;
&lt;p&gt;However, it wasn't obvious to some of the users that they also had to have their group password set for the &lt;code&gt;htaccess&lt;/code&gt; file to work. So we &amp;ldquo;fixed&amp;rdquo; that by putting a notice on a couple key pages that give a link and dirctions to setting the password.&lt;/p&gt;
&lt;p&gt;Obviously, once we figure out how OIDC can work with static sites, we'll provide that also. But, let's be honest, we aren't moving very quickly on this project.&lt;/p&gt;
&lt;h2&gt;Refactoring&lt;/h2&gt;
&lt;p&gt;This month's coding involved a couple nasty refactoring.&lt;/p&gt;
&lt;p&gt;THe first was changing our template system. While we were using &lt;a href="https://docs.rs/minijinja/latest/minijinja/"&gt;Minijinja&lt;/a&gt; for everything, it was getting a bit cumbersome (pain point on our case). We've switched Minijinja doing the page-level templating and generate the controls using &lt;a href="https://maud.lambda.xyz/"&gt;Maud&lt;/a&gt;. It made it a lot easier to manage the code and catch typos.&lt;/p&gt;
&lt;p&gt;But it also lead into our other refactor: challenges. Challenges were always intended to be part of the system. The idea of having a pluggable system for handling log in and registration was appealing, such as introducing an &lt;a href="https://github.com/TecharoHQ/anubis"&gt;Anubis-like&lt;/a&gt; system for registration that automatically scales with load, or allow something to register with a SSH or GnuPG key instead of email has been one of our goals.&lt;/p&gt;
&lt;p&gt;However, in the process of getting April's release, we ended up writing the login/registration process as a state machine. Well, switching to Maud made the checkbox to change password not work out well. So to do that, we migrated to challenges, which then encouraged us to also change our database strategy because of another pain point. So, now we can introduce a number of challenge steps into a generic work flow.&lt;/p&gt;
&lt;p&gt;And that lead to us introducing &lt;a href="https://www.sea-ql.org/SeaORM/"&gt;SeaORM&lt;/a&gt; into the system. Originally we worked directly with &lt;a href="https://docs.rs/sqlx/latest/sqlx/"&gt;SQLx&lt;/a&gt; and hand-written SQL statements but that got painful. We decided to go with SeaORM to reduce the boilerplate we were seeing in the code that made things a little painful.&lt;/p&gt;
&lt;h2&gt;Membership Expiration&lt;/h2&gt;
&lt;p&gt;One of the &amp;ldquo;we would really like this feature now&amp;rdquo; that we go was expiring groups. This is because some people pay directly and want access? Well, not entirely sure, but we implemented it. Since Leicmin is still pretty rough, the membership expiration has to be done via the CLI but it should work to let someone introduct temporary memberships.&lt;/p&gt;
&lt;h2&gt;Integration Tests&lt;/h2&gt;
&lt;p&gt;This isn't really useful for most people, but we figured out how to mock out the Patreon and Subscribe Star API, so we're in the process of also making sure those have some integration tests going because that section was always a manual effort.&lt;/p&gt;
&lt;h2&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Overall, there wasn't a lot of obvious changes in the code (except for the challenges). But, the code base is a lot easier to work with and hopefully that will set us up for the next few steps.&lt;/p&gt;
&lt;p&gt;From the alpha tester, the biggest ones are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Email registration&lt;/li&gt;
&lt;li&gt;Password recovery&lt;/li&gt;
&lt;li&gt;Single sign-on for Patreon and Subscribe Star&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our longer-term goals are still frustrating that they are needed, but there is clearly a desire for it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Group membership limits (e.g., age verification)&lt;/li&gt;
&lt;li&gt;Auditing (legal requirements of some of the age verification laws)&lt;/li&gt;
&lt;li&gt;Geo blocking (so someone can do the Pornhub thing of banning entire states)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No idea of when the next time we'll circle around to this. Mostly depends on folks being interested, asking questions, or installing the &lt;em&gt;still alpha-quality&lt;/em&gt; instance and try to use it. Other than that, we'll just keep plugging away.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Deprecations and Updates (MfGames .NET Libraries)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/07/04/deprecations-and-updates/" />
    <updated>2026-07-04T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/07/04/deprecations-and-updates/</id>
    <category term="mfgames-net-libraries" scheme="https://mfgames.com/categories/" label="MfGames .NET Libraries" />
    <content type="html">&lt;p&gt;It's been a while, but we had a productive week making changes to the library.&lt;/p&gt;
&lt;h2&gt;Deprecations&lt;/h2&gt;
&lt;p&gt;One advantage of having a monorepo of all of the &lt;a href="//mfgames.com/mfgames-cil/"&gt;MfGames&lt;/a&gt; packages is being able to link everything together without doing a railcar shuffle when one package requires another and another and takes half a day to update a dependency. The drawback is that it makes it harder to abandon one of the libraries. Today is one of those days where we realize there are three libraries that need to be dropped from active development.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MfGames.Identifiers&lt;/code&gt; was a good idea at the time but then we found that &lt;a href="//d.moonfire.us/blog/2026/01/04/other-smarter-people/"&gt;other libraries can handle the same goals&lt;/a&gt; and we don't have to maintain ours. Also, working with source generated files is painful so we are thinking those libraries are a better choice for now.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MfGames.Nitride.SpectreConsole&lt;/code&gt; was an attempt to funnel Serilog output through SprectreConsole so we could get progress bars playing well with output for &lt;code&gt;MfGames.Nitride&lt;/code&gt;. It ended up being cumbersome and, while it worked, it also had some minor quirks that were irriating enough that we just gave up on the idea of progress bars entirely instead of trying to fix it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MfGames.Serilog.SpectreExpressions&lt;/code&gt; was the underlying library for the &lt;code&gt;MfGames.Nitride.SpectreConsole&lt;/code&gt;. This ended up being a mirror of the existing expressions library from Serilog because the original was sealed and couldn't be extended without copying the wheel. It also didn't have the full ability as the original library because it was hard to maintain code that isn't ours while needing to track any vendor changes. So when &lt;code&gt;MfGames.Nitride.SpectreConsole&lt;/code&gt; was deprecated, so was this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, we don't want to abandon old code entirely, so we moved those projects from the &lt;code&gt;//src&lt;/code&gt; directory into the &lt;code&gt;//attic/&lt;/code&gt; directory. That way, the code remains available in case we find another use of that. This also means that those projects will &amp;ldquo;bit rot&amp;rdquo; in that they won't be refactored and maintained with the code in &lt;code&gt;//src/&lt;/code&gt;, &lt;code&gt;//tests/&lt;/code&gt;, and &lt;code&gt;//examples/&lt;/code&gt;. The more time passes on, they will become less useful as code until we clean out the attic, which may be years from now.&lt;/p&gt;
&lt;h2&gt;Package Updates&lt;/h2&gt;
&lt;p&gt;In addition, we've done a round of updates to keep everything running smoothly and handle some vulerability warnings in our dependencies. Overall, this was a simply process except libraries dealing with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MfGames.Nitride.IO&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the CLI, we use &lt;a href="https://github.com/dotnet/command-line-api"&gt;System.CommandLine&lt;/a&gt; as the base of our &lt;code&gt;MfGames.ToolBuilder&lt;/code&gt; library, which is basically an opionated setup that uses &lt;code&gt;Serilog&lt;/code&gt;, &lt;code&gt;Autofac&lt;/code&gt;, and dependecy-injected commands. When moving from version &lt;code&gt;2.0.0-beta8&lt;/code&gt; (it was in beta for over five years) to &lt;code&gt;2.0.9&lt;/code&gt;, there were some drastic refactoring that needed to be done. This resulted in all the CLI commands (&lt;code&gt;MfGames.ToolBuilder&lt;/code&gt; and &lt;code&gt;MfGames.Nitride&lt;/code&gt;) having a breaking change since the signatures changed across the board. There were relatively few dependencies on this, but they were all bumped to major versions.&lt;/p&gt;
&lt;p&gt;For the other libraries, this was a feature (0.x.0) update because of potential side effects we haven't been able to identify.&lt;/p&gt;
&lt;h3&gt;MfGames.Nitride.IO&lt;/h3&gt;
&lt;p&gt;The other change was &lt;code&gt;MfGames.Nitride.IO&lt;/code&gt;. In specific, we changed how the &lt;code&gt;ReadFiles&lt;/code&gt; operation worked because of real-world difficulties. Most of our IO operations are based on the excellent library, &lt;a href="https://github.com/xoofx/zio"&gt;Zio&lt;/a&gt; which gives use &lt;code&gt;UPath&lt;/code&gt; which simplifies path processing, virtual file systems which makes test writing a lot easier, and remapped structures that let us treat a folder as &lt;code&gt;/sources/allegro&lt;/code&gt; as &lt;code&gt;/&lt;/code&gt; so our &lt;a href="//fedran.com"&gt;Fedran&lt;/a&gt; can treat each project Git repository as a root folder, which simplifies logic. We also like using &lt;a href="https://github.com/kthompson/glob/"&gt;recursive globbing&lt;/a&gt; (ones that use &lt;code&gt;**&lt;/code&gt; to recurse into dirctories in addition to &lt;code&gt;*&lt;/code&gt; for matching at a given level).&lt;/p&gt;
&lt;p&gt;The problem is that Zio doesn't support globbing natively. So our original implementation was to iterate through every file using Zio, then compare it against the glob.&lt;/p&gt;
&lt;p&gt;For simple cases, this was fast. However, when we're doing a search like &lt;code&gt;/src/**/chapter-*.md&lt;/code&gt; in a project directory, this also processed through &lt;code&gt;/node_modules&lt;/code&gt;, &lt;code&gt;.direnv/&lt;/code&gt;, and &lt;code&gt;target/&lt;/code&gt; folders which have a staggering amount of files that we don't want.&lt;/p&gt;
&lt;p&gt;Nowe, we have both include and exclude patterns in an ordered list. For each one, we parse the pattern and attempt to do some simple heuristics with the root comparisons before we devolve into the &amp;ldquo;grab everything to compare it&amp;rdquo;. What this means that for &lt;code&gt;/src/**/chapter-*.md&lt;/code&gt;, we go into &lt;code&gt;/src&lt;/code&gt; before we grab everything. The other massive folders are looked at, and the code chooses not to go into it. If there are no &lt;code&gt;**&lt;/code&gt; in the pattern, this will give us a rather prescise scan which is faster than before.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;// Old version
ReadFiles readFiles = Resolve&amp;lt;ReadFiles&amp;gt;()
  .WithPattern(&amp;quot;/chapters/chapter-*.md&amp;quot;);

// Old version without using the .With* methods.
ReadFiles readFiles2 = Resolve&amp;lt;ReadFiles&amp;gt;();
readFiles2.Pattern = &amp;quot;/chapters/chapter-*.md&amp;quot;;

// New Version
ReadFiles readFiles = Resolve&amp;lt;ReadFiles&amp;gt;()
    .AddIncludePattern(&amp;quot;/chapters/chapter-*.md&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep the conventions from &lt;code&gt;.gitignore&lt;/code&gt; and other files, if you give the pattern as &lt;code&gt;*.txt&lt;/code&gt;, we change it to &lt;code&gt;/**/*.txt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Which leads to the exclude patterns. &lt;code&gt;/**&lt;/code&gt; effectively goes back to the old code of grabbing everything and comparing it. But if you then add an exclude pattern (ideally without a &lt;code&gt;**&lt;/code&gt;), it will stop processing the includes if it already knows it will be excluding it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-csharp"&gt;ReadFiles readFiles = Resolve&amp;lt;ReadFiles&amp;gt;()
    .AddIncludePattern(&amp;quot;/**/chapters/chapter-*.md&amp;quot;)
    .AddExcludePattern(&amp;quot;/node_modules&amp;quot;)
    .AddExcludePatternList(
        &amp;quot;/.direnv&amp;quot;,
        &amp;quot;/target&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will eventually lead up to being able to pass in &lt;code&gt;.gitignore&lt;/code&gt; directly into the code, but we want the code base to settle (e.g., use it for awhile) before we add that.&lt;/p&gt;
&lt;p&gt;There is also a &lt;code&gt;.WithPatternList()&lt;/code&gt; which takes a list of a typesafe collection, but that's a more edge case and if you need that, then you might want to look at our tests for examples of how to write those.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Some Quality of Life Changes (Fiss)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/06/13/some-quality-of-life/" />
    <updated>2026-06-13T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/06/13/some-quality-of-life/</id>
    <category term="fiss" scheme="https://mfgames.com/categories/" label="Fiss" />
    <content type="html">&lt;p&gt;It's been over a year since there were any major changes to &lt;a href="//mfgames.com/fiss/"&gt;fiss&lt;/a&gt;, mostly because it did the minimum functionality &lt;a href="//d.moonfire.us"&gt;we&lt;/a&gt; wanted and there are no other reported issues. However, that isn't to say it was perfect or not filled with bugs (you know, like &lt;code&gt;fiss group list&lt;/code&gt; throwing a panic because it was never implemented).&lt;/p&gt;
&lt;p&gt;Since we had a little time to work on something, we decided to address some quality of life issues that will hopefully make it more pleasant.&lt;/p&gt;
&lt;h2&gt;Progress Bars&lt;/h2&gt;
&lt;p&gt;When &lt;code&gt;fiss&lt;/code&gt; is pointed to large collections of repositories, like Dylan's fiction sites, there are hundreds of repositories. When running &lt;code&gt;fiss cache sync&lt;/code&gt;, it would take significant time to run because it walks through every server, group, and project that user is subscribed to. For us, we have hundreds and this takes a while because our server isn't that fast.&lt;/p&gt;
&lt;p&gt;To give some feedback, instead of showing nothing for 30-90 seconds, we implemented a nested progress bar that shows the process.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ fiss cache sync
sync_server_by_name server_name=&amp;quot;mfgames&amp;quot;                                                                 3.9s
  ↳ sync_group_by_names server_name=&amp;quot;mfgames&amp;quot; group_name=&amp;quot;fedran-sources&amp;quot;                                 1.8s
    ↳ sync_project_by_names server_name=&amp;quot;mfgames&amp;quot; group_name=&amp;quot;fedran-sources&amp;quot; project_name=&amp;quot;a-simple-cog&amp;quot; 0.1s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This only shows up if the command is entered into a terminal but it can be disabled with &lt;code&gt;--show-progress never&lt;/code&gt;. In addition, &lt;code&gt;always&lt;/code&gt; forces it to run even outside of a terminal, and &lt;code&gt;auto&lt;/code&gt; does its best guess to determine if it is attached to a terminal or not.&lt;/p&gt;
&lt;p&gt;The output isn't &amp;ldquo;pretty&amp;rdquo; but it is functionality for our purposes. The output is sent to &amp;ldquo;standard error&amp;rdquo; so redirect a command to get the JSON output won't be polluted by the output, even if &lt;code&gt;--show-progress always&lt;/code&gt; is used. (Likewise, all console logging is also always sent to standard error for the same reason.)&lt;/p&gt;
&lt;h2&gt;More Intelligent Syncing&lt;/h2&gt;
&lt;p&gt;The above was written to help debug the one feature we were working on: being more graceful about how we sync our information with the forge. Previously, the only way to get a fresh list of issues was to use the &lt;code&gt;fiss cache sync&lt;/code&gt; command which took some time.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ time fiss cache sync --forge-sync always

real	0m46.598s
user	0m5.533s
sys	0m0.397s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you can't guess from the spoiler, we have the same options: &lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;never&lt;/code&gt;, and &lt;code&gt;always&lt;/code&gt; implemented here.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ time fiss cache sync --forge-sync never

real	0m3.127s
user	0m3.077s
sys	0m0.104s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It still walks through the servers and projects, but shaving forty seconds because it doesn't have to do API calls to the forge is a significant time savings.&lt;/p&gt;
&lt;p&gt;When using &lt;code&gt;auto&lt;/code&gt;, the default, the system now keeps track of the last time a server, group, or project was syned. It only syncs if the server or group hasn't been refreshed in a week or the project hasn't been refreshed in a day.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;time fiss cache sync

real	0m3.126s
user	0m3.076s
sys	0m0.103s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That should make that aspect more useful.&lt;/p&gt;
&lt;h2&gt;Refresh On List&lt;/h2&gt;
&lt;p&gt;The third and final change was implementing syncing when running the list command. As said above, the only way to get new issues from the web was to run &lt;code&gt;fiss cache sync&lt;/code&gt;. Now, &lt;code&gt;fiss cache list&lt;/code&gt; also refreshes but it is more intelligent about it based on the scope.&lt;/p&gt;
&lt;p&gt;For example, if only a project is requested, then it only syncs that project which takes considerably less time to implement (1-2 API calls at this point and usually under 100 ms with our current setup). However, if a group is requested, then it will only sync all the projects under that group.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;fiss list --group fedran-sources
 id                                                title                                               state  assigned
 mfgames/fedran-sources/allegro/7                  Get automatic versioning working                    open
 mfgames/fedran-sources/allegro/6                  Get it sent to the copywriter                       open
 mfgames/fedran-sources/allegro/5                  Get it sent to the development editor               open
 mfgames/fedran-sources/allegro/4                  Create a cover a cover                              open
 mfgames/fedran-sources/coins-for-your-troubles/1  Chapters 12 and 13 need an editing pass             open
 mfgames/fedran-sources/flight-of-the-scions/2     Go through and update the various content warnings  open
 mfgames/fedran-sources/flight-of-the-scions/1     Make the book public                                open
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if &lt;code&gt;fiss cache list --server mfgames&lt;/code&gt; is used, then it will sync all the groups and projects on that server before showing the list. Since this is automatic done (baring the use of &lt;code&gt;--forge-sync&lt;/code&gt;), then &lt;code&gt;fiss list&lt;/code&gt; will always get the most recent copy of the tasks that need to be done.&lt;/p&gt;
&lt;p&gt;As, if the command is used without options, it picks up the project from the current Git repository which means &lt;code&gt;fiss list&lt;/code&gt; is useful when inside that project's directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ cd fiss
$ time fiss list
 id                    title                                                                       state  assigned
 mfgames/fiss/fiss/16  Get flake working as input for another flake                                open
 mfgames/fiss/fiss/15  Do not show archived projects by default                                    open
 mfgames/fiss/fiss/13  Implement subscriptions                                                     open
 mfgames/fiss/fiss/11  Allow for --body/-b to edit the body text                                   open
 mfgames/fiss/fiss/10  Show information after creating a new issue                                 open
 mfgames/fiss/fiss/8   Show progress while syncing with server                                     open
 mfgames/fiss/fiss/7   Refresh servers, groups, and projects periodically                          open
 mfgames/fiss/fiss/6   Cache foregejo API handle                                                   open
 mfgames/fiss/fiss/20  Handle fiss list fetching from empty set                                    open
 mfgames/fiss/fiss/21  Better handling when a server is removed from the config                    open
 mfgames/fiss/fiss/22  Allow output of URL or ID when creating a new issue                         open
 mfgames/fiss/fiss/23  Configuration option for how frequently to get server, groups, or projects  open
 mfgames/fiss/fiss/24  Allow sync from list to retrieve new groups and projects found              open
 mfgames/fiss/fiss/25  Change sync to be based on *_cache instead of by name                       open
 mfgames/fiss/fiss/26  Implement issue-level syncing                                               open
 mfgames/fiss/fiss/27  Figure out why Radicle will not sync                                        open

real	0m0.036s
user	0m0.030s
sys	0m0.009s
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Future&lt;/h2&gt;
&lt;p&gt;We still use this project almost every day to manage our lists, but since we're the only people appearing to use it, features will show up as we need them or something annoys us. As usual, reporting issues is already appreciated but since we have to periodically disable registration on our forge, we'll be looking into better ways of submitting issues, having discussions, or getting patches. Until then, use &lt;a href="//d.moonfire.us/contact/"&gt;Dylan's contact page&lt;/a&gt; or &lt;a href="mailto:contact@mfgames.com"&gt;contact@mfgames.com&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>v1.0.0 - Breaking Changes (MfGames Writing Setup Flake)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/04/16/breaking-changes/" />
    <updated>2026-04-16T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/04/16/breaking-changes/</id>
    <category term="mfgames-writing-setup-flake" scheme="https://mfgames.com/categories/" label="MfGames Writing Setup Flake" />
    <content type="html">&lt;p&gt;As the chained updates go through, we've managed to update this project to use the new options-based format of &lt;a href="https://mfgames.com/mfgames-project-setup-flake/"&gt;mfgames-project-setup-flake&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This release changes a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We are now switching to &lt;a href="https://semver.org/"&gt;semver&lt;/a&gt; with this being tagged as v1.0.0&lt;/li&gt;
&lt;li&gt;Moving from the &lt;code&gt;mkConfig&lt;/code&gt; to a NixOS-style configuration&lt;/li&gt;
&lt;li&gt;Embracing Justfile as the default build process&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Importing&lt;/h2&gt;
&lt;p&gt;Importing is slightly differen than before. Inputs should include the tags:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;flake-parts.url = &amp;quot;github:hercules-ci/flake-parts&amp;quot;;
mfgames-writing-setup.url = &amp;quot;git+https://src.mfgames.com/nixos-contrib/mfgames-writing-setup-flake.git?ref=refs/tags/v1.0.0&amp;quot;;
mfgames-writing-setup.inputs.nixpkgs.follows = &amp;quot;nixpkgs&amp;quot;; # Optional
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This flake needs to be imported into the module:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;  outputs =
    inputs@{ flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = [
        inputs.mfgames-writing-setup.flakeModules.default
      ];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While we haven't figured out how to get this to play with &lt;a href="https://github.com/numtide/flake-utils"&gt;flake-utils&lt;/a&gt;, we've changed our flake to use &lt;a href="https://flake.parts/"&gt;flake-parts&lt;/a&gt; which gave us a better framework for figuring out the options. As a note, configuration is done &lt;em&gt;outside&lt;/em&gt; of the &lt;code&gt;perSystem&lt;/code&gt;, but the results (&lt;code&gt;config.mfgames.writing&lt;/code&gt;) are provided inside &lt;code&gt;perSystem&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;      mfgames.writing.enable = true;

      perSystem =
        { config, pkgs, ... }:
        {
          devShells.default = pkgs.mkShell {
            packages = [ ] ++ config.mfgames.writing.packages;
            shellHook = config.mfgames.writing.shellHook;
          };
        };
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Project Setup&lt;/h2&gt;
&lt;p&gt;If you notice, this no longer requires adding the &lt;code&gt;mfgames-project-setup-flake&lt;/code&gt; settings, this flake will now automatically include that unless you need to specifically override it. However, you don't have to import the project setup if you want to use its current functionality, such as using &lt;code&gt;mfgames.project.reuse&lt;/code&gt; to license your work:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;      mfgames.project.reuse.enable = true;
      mfgames.writing.enable = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At the moment, there is no specific settings under &lt;code&gt;mfgames.writing&lt;/code&gt; other than &lt;code&gt;enable&lt;/code&gt;. The above example still imports everything you need to set up &lt;a href="https://mfgames.com/mfgames-writing-js/"&gt;mfgames-writing-js&lt;/a&gt; except that library itself. That is going to be the next change, to automatically package everything including the JS libraries, but not quite yet. Until then, you still need to set up your &lt;code&gt;package.json&lt;/code&gt; to include those projects. When that does it, this will be another breaking change because 99% of the time, anyone using this flake is also going to be using &lt;code&gt;mfgames-writing&lt;/code&gt; to build the projects.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Semantic Versioning (Leicmin)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/04/12/semantic-versioning/" />
    <updated>2026-04-12T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/04/12/semantic-versioning/</id>
    <category term="leicmin" scheme="https://mfgames.com/categories/" label="Leicmin" />
    <content type="html">&lt;p&gt;&lt;a href="//d.moonfire.us/blog/2026/04/04/leicmin/"&gt;Six months ago&lt;/a&gt;, we started on a new project called &lt;a href="/leicmin/"&gt;Leicmin&lt;/a&gt; to provide an application for managing members of subscriber groups along with a naieve implementation of age verification checks for those who enjoy the &amp;ldquo;spicer&amp;rdquo; side of creation.&lt;/p&gt;
&lt;p&gt;We were lucky to have an early adopter who helped us iron out some of the more egregarious bugs that were in the application, but there is a still a long way to go. But it is nice to have an instance &amp;ldquo;in the wild&amp;rdquo; as it were.&lt;/p&gt;
&lt;p&gt;This also marks the point where Lecimin is going to have proper semantic versioning, at least for releases. This is important because there was a lot of going back and redoing Postgres migrations which would then require a full install to work.&lt;/p&gt;
&lt;p&gt;Moving forword (e.g., after v0.3.0), the retroactive database changes will only happen between one tag and another. So, if you do use it (and &lt;a href="//d.moonfire.us/contact/"&gt;let us know&lt;/a&gt; if you want to), stick with version tags instead of going directly against &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Breaking Changes (MfGames Project Setup Flake)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/04/05/breaking-changes/" />
    <updated>2026-04-05T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/04/05/breaking-changes/</id>
    <category term="mfgames-project-setup-flake" scheme="https://mfgames.com/categories/" label="MfGames Project Setup Flake" />
    <content type="html">&lt;p&gt;In the beginning, I wanted to use NixOS options with the flake instead of using
a &lt;code&gt;mkConfig&lt;/code&gt; function to generate the files and shell hooks. But, due to
complexities and struggling with Nix, I couldn't figure it out. But, after two
years of working on it, I finally got a working implementation that uses the
cleaner approach of options.&lt;/p&gt;
&lt;p&gt;So that means instead of using &lt;code&gt;mkLib&lt;/code&gt;, using
&lt;a href="//mfgames.com/mfgames-project-setup-flake/"&gt;mfgames-project-setup-flake&lt;/a&gt; means
adding it as an input (as before), importing the model (that's new), and then
using configuration settings (those are new).&lt;/p&gt;
&lt;p&gt;The flake still needs to be added as an input:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;inputs.mfgames-project-setup.url = &amp;quot;git+https://src.mfgames.com/nixos-contrib/mfgames-project-setup-flake.git&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It also now has a module which needs to be imported and then configured using
the same syntax that most other configurations are used.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;imports = [
    inputs.mfgames-project-setup.flakeModules.default
];

mfgames.project = {
    enable = true;
    rust.enable = true;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, in the &lt;code&gt;mkShell&lt;/code&gt; call, the various hooks are called to integrate it.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;devShells.default = pkgs.mkShell {
    packages = []
        ++ config.mfgames.project.packages;

    shellHook = config.mfgames.project.shellHook;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also refactored how
&lt;a href="//mfgames.com/mfgames-project-setup-flake/docs/options/"&gt;options&lt;/a&gt; where
documented and displayed. It is now a script-generated file that is rebuild
during the build process so I don't have to worry about getting it stale.&lt;/p&gt;
&lt;h2&gt;Versions&lt;/h2&gt;
&lt;p&gt;The old version is now &lt;code&gt;v0.5.0&lt;/code&gt; with the new version current at &lt;code&gt;v1.1.0&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Breaking Changes&lt;/h2&gt;
&lt;p&gt;Since this is a really breaks existing setups, I'm also taking the opportunity
to make other changes that have popped up in the last few years that I didn't
want to be disruptive but I think will benefit the project in the long run.&lt;/p&gt;
&lt;h3&gt;REUSE&lt;/h3&gt;
&lt;p&gt;Probably the biggest one is stumbling onto
&lt;a href="https://reuse.software/"&gt;REUSE Software&lt;/a&gt; and seeing a more nuanced (though
complicated) specification for handling licenses that differ across files even
in the same project. That is osmething I couldn't do but it resolved a number of
issues I had after trying to integrate
&lt;a href="https://mfgames.com/mfgames-writing-setup-flake/"&gt;mfgames-writing-project-setup&lt;/a&gt;
which is less code-oriented and more prose-oriented. After mulling over it, I
decided to remove looking over the differencesand reasoning, I decided to
integrate REUSE into the flake and remove the existing licensing handling.&lt;/p&gt;
&lt;p&gt;This means removing the following keys from before:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;creativeCommonsAttributionShareAlike.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;creativeCommonsAttributionNonCommercialShareAlike.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mit.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mit.filename&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And replacing it with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.reuse.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.reuse.ignore-dot-config&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.reuse.annotations&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most complicated part is that REUSE is &amp;ldquo;picky&amp;rdquo;. It requires every file to be
properly licensed, so I introduced the ability to create the &lt;code&gt;REUSE.toml&lt;/code&gt;
annotations from the flake file which will be suppelemented by the automatic
annotations from the generated files this flake produces.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;mfgames.project.reuse.annotations = [
    {
        path = [
            &amp;quot;docs/**/*.md&amp;quot;
            &amp;quot;news/**/*.md&amp;quot;
            &amp;quot;README.md&amp;quot;
        ];

        SPDX-FileCopyrightText = &amp;quot;2026 Dylan Moonfire &amp;lt;contact@mfgames.com&amp;gt;&amp;quot;;
        SPDX-License-Identifier = &amp;quot;CC-BY-4.0&amp;quot;;
    }
    {
        path = &amp;quot;src/**/*.nix&amp;quot;;
        SPDX-FileCopyrightText = &amp;quot;2026 Dylan Moonfire &amp;lt;contact@mfgames.com&amp;gt;&amp;quot;;
        SPDX-License-Identifier = &amp;quot;MIT&amp;quot;;
    }
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also consider most of the contents in &lt;code&gt;.config&lt;/code&gt; to be generated files, so an
annotation is added to mark all of those files as public domain unless turned
off with &lt;code&gt;mfgames.project.reuse.ignore-dot-config = false;&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Developer Certificate of Origin&lt;/h3&gt;
&lt;p&gt;I modified the setup so it adds the &amp;ldquo;Signed-off-by&amp;rdquo; to the bottom of Git commit
messages if `mfgames.project.developerCertificateOfOrigin.enable = true&amp;quot;.&lt;/p&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;p&gt;I prefer a more vertical style of formatting in my code. That usually means I
want braces on their own line for almost everything, including if statements.
This was &amp;ldquo;on the same line&amp;rdquo; in the previous version, but now it defaults to
braces on their own line.&lt;/p&gt;
&lt;h2&gt;Non-Breaking Changes&lt;/h2&gt;
&lt;p&gt;I found a consistent logging program that gives useful messages:
&lt;a href="https://github.com/charmbracelet/gum"&gt;gum&lt;/a&gt;. While the colors are not exactly my
thing, I find it useful to have the same type of messages across the board. So
you'll see a variety of functions now use that for logging, when easily done.&lt;/p&gt;
&lt;h3&gt;Project UUID&lt;/h3&gt;
&lt;p&gt;A few years ago, I stumbled onto a blog post about creating a &amp;ldquo;power ID&amp;rdquo; for
projects. In effect, a UUIDv7 that uniquely identifies a project and make it
easier to distinguish projects of the same same (such as Gemini the Protocol,
Gemini the Cryptocurrency, and Gemini the LLM). That is why you see
&lt;code&gt;019530bd-5065-731f-925b-3961eb7aa613&lt;/code&gt; in almost every blog post and article I
write about &lt;code&gt;mfgames-project-setup-flake&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since I've embraced that, I also modified this project to generate one if there
is no &lt;code&gt;.config/project-uuid.txt&lt;/code&gt; present in the project. This is intended to be
checked in since it shouldn't change.&lt;/p&gt;
&lt;p&gt;This functionality can be turned off with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-nix"&gt;mfgames.project.project-uuid.enable = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Shell Formatting and Checking&lt;/h3&gt;
&lt;p&gt;I also recently started using &lt;code&gt;shfmt&lt;/code&gt; and &lt;code&gt;shellcheck&lt;/code&gt; for my shell scripts.
I've introduced some options for this.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.shell.enable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.shell.check&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.shell.format&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mfgames.project.shell.includes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;check&lt;/code&gt; and &lt;code&gt;format&lt;/code&gt; use &lt;code&gt;enable&lt;/code&gt; if not defined. And &lt;code&gt;enable&lt;/code&gt; defaults to
&lt;code&gt;mfgames.project.enable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;check&lt;/code&gt; is true, then &lt;code&gt;just check-shellcheck&lt;/code&gt; or &lt;code&gt;just check&lt;/code&gt; will run that
utility against the files.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;format&lt;/code&gt; is true, then &lt;code&gt;just format&lt;/code&gt; and &lt;code&gt;just format-shfmt&lt;/code&gt; will format the
shell script, as will commiting a file.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A Slew of (Breaking) Fixes (MfGames Writing Tools)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2026/01/17/a-slew-of-breaking-fixes/" />
    <updated>2026-01-17T06:00:00Z</updated>
    <id>https://mfgames.com/blog/2026/01/17/a-slew-of-breaking-fixes/</id>
    <category term="mfgames-writing-tools" scheme="https://mfgames.com/categories/" label="MfGames Writing Tools" />
    <content type="html">&lt;p&gt;We've just pushed up a new version of &lt;a href="//mfgames.com/mfgames-writing-js/"&gt;mfgames-writing-js&lt;/a&gt;. Naturally, this started with the usual process of doing a little housekeeping, and bringing packages up to date.&lt;/p&gt;
&lt;p&gt;The main focus was to fix two bugs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://src.mfgames.com/mfgames-writing-js/mfgames-writing-js/issues/3"&gt;&lt;code&gt;.docx&lt;/code&gt; was generating hyphenation would caused problems with readers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://src.mfgames.com/mfgames-writing-js/mfgames-writing-js/issues/2"&gt;Using &amp;lsquo;&amp;mdash;&amp;rsquo; for section breaks fails to render breaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://src.mfgames.com/mfgames-writing-js/mfgames-writing-js/issues/4"&gt;&lt;code&gt;.docx&lt;/code&gt; generation couldn't handle sub directories&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, we had to make a breaking change and bumped the major version of some of the packages, which also requires at least a minor update to the other changes to pick up the new version ranges.&lt;/p&gt;
&lt;p&gt;And then what followed was an embarassing set of NPM package updates as we tried to get everything working consistently and feeding into dependent projects like &lt;a href="https://src.mfgames.com/nixos-contrib/fedran-writing-setup-flake"&gt;fedran-writing-setup-flake&lt;/a&gt; which is a mass setup scrip for all the writing projects on &lt;a href="//fedran.com"&gt;fedran.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Along the way, we also introduced &lt;code&gt;--log-level&lt;/code&gt; support so &lt;code&gt;mfgames-writing-format build&lt;/code&gt; wasn't quite as noisy.&lt;/p&gt;
&lt;h2&gt;Short Version&lt;/h2&gt;
&lt;p&gt;If you use &lt;code&gt;mfgames-writing-js&lt;/code&gt;, update all your packages to the latest and things should Just Work™. If they don't, please don't hesitate to &lt;a href="https://src.mfgames.com/mfgames-writing-js/mfgames-writing-js/"&gt;report an issue&lt;/a&gt; or &lt;a href="https://d.moonfire.us/contact/"&gt;contact Dulan&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Pipeline Exclusions&lt;/h2&gt;
&lt;p&gt;Added an &lt;code&gt;exclude&lt;/code&gt; to pipelines in the same pattern as the content elements:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;contents:
  - element: chapter
    source: pipeline.md
    pipeline:
      - module: &amp;quot;@mfgames-writing/hyphen-pipeline&amp;quot;
        exclude:
          editions: [docx]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This caused a difficulty with an undocumented element in &lt;code&gt;hyphen-pipeline&lt;/code&gt; which allowed for a regular expression to ignore certain words that caused problem with hyphenation. This was moved to &lt;code&gt;exclude.regex&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;contents:
  - element: chapter
    source: pipeline.md
    pipeline:
      - module: &amp;quot;@mfgames-writing/hyphen-pipeline&amp;quot;
        exclude:
          editions: [docx]
          regex: [&amp;quot;^bob$&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because this changed some projects in use, it was treated as a breaking change and bumped the version.&lt;/p&gt;
&lt;h3&gt;Section Breaks&lt;/h3&gt;
&lt;p&gt;When we write in Markdown, we always use &lt;code&gt;***&lt;/code&gt; for section breaks.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-markdown"&gt;---
title: Chapter One
---

Something happened.

---

After the break.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, proejcts like &lt;a href="https://mfgames.com/mfgames-writing-setup-flake/"&gt;mfgames-writing-setup-flake&lt;/a&gt; use &lt;a href="https://prettier.io/"&gt;prettier&lt;/a&gt; which always converted the &lt;code&gt;***&lt;/code&gt; into &lt;code&gt;---&lt;/code&gt; which is also a proper Markdown rule (which we use as a section break in stories).&lt;/p&gt;
&lt;p&gt;Due to interactions with &lt;a href="https://www.npmjs.com/package/smartypants"&gt;SmartyPaths&lt;/a&gt;, those &lt;code&gt;---&lt;/code&gt; were helpfully turned into an em-dash (—). We put in a little patch code that an em-dash on a line by itself is considered a rule for section breaks and turned back into &lt;code&gt;---&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Output Directory&lt;/h2&gt;
&lt;p&gt;Another thing that we've done with &lt;code&gt;mfgames-writing-setup-flake&lt;/code&gt; is put all the output files into the &lt;code&gt;build/release&lt;/code&gt; folder to keep them out of the root directory of the repository. But there was a little but where &lt;code&gt;.docx&lt;/code&gt; files couldn't handle that.&lt;/p&gt;
&lt;p&gt;We fixed it.&lt;/p&gt;
&lt;h2&gt;Logging&lt;/h2&gt;
&lt;p&gt;Also, it was a little annoying having so much data that we added a &lt;code&gt;--log-level&lt;/code&gt; to control output. It defaults to &lt;code&gt;info&lt;/code&gt; but can also be &lt;code&gt;trace&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, &lt;code&gt;warn&lt;/code&gt;, and &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Updating Dependencies (MfGames Conventional Commit)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2025/12/05/updating-dependencies/" />
    <updated>2025-12-05T06:00:00Z</updated>
    <id>https://mfgames.com/blog/2025/12/05/updating-dependencies/</id>
    <category term="mfgames-conventional-commit" scheme="https://mfgames.com/categories/" label="MfGames Conventional Commit" />
    <content type="html">&lt;p&gt;Just a little maintenance update.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Updated the Nix flake from 25.05 to 25.11&lt;/li&gt;
&lt;li&gt;Updated Rust to the 2025-12-05 nightly&lt;/li&gt;
&lt;li&gt;Updated various dependency packages&lt;/li&gt;
&lt;li&gt;Bumped the version to 0.3.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing really fancy, just housekeeping.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Making It Useful (MfGames Conventional Commit)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2025/08/12/making-it-useful/" />
    <updated>2025-08-12T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2025/08/12/making-it-useful/</id>
    <category term="mfgames-conventional-commit" scheme="https://mfgames.com/categories/" label="MfGames Conventional Commit" />
    <content type="html">&lt;p&gt;We wrote &lt;code&gt;mfgames-conventional-commits&lt;/code&gt; to solve a small itch of wanting to be able to calculate version numbers based on &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt; but only for commits that touch specific directories. That way, a change to &lt;code&gt;MfGames.IO&lt;/code&gt; would bump that version up but not touch the versioning for &lt;code&gt;MfGames.Nitride&lt;/code&gt; in the same &lt;a href="//mfgames.com/mfgames-cil/"&gt;monorepo&lt;/a&gt;. For the most part, it was to allow the CIL projects to have different versions as they all had different lineages before they were combined.&lt;/p&gt;
&lt;p&gt;As things go, we always intended to &lt;a href="https://src.mfgames.com/mfgames-git/mfgames-conventional-commit-rs/issues"&gt;polish&lt;/a&gt; and expand on the tool but other things got in the way. But then &lt;a href="//d.moonfire.us/"&gt;D. Moonfire&lt;/a&gt; was working on &lt;a href="//https://d.moonfire.us/garden/git-for-authors/"&gt;Git For Authors&lt;/a&gt; and realized they didn't have a good solution for calculating version numbers of novels. Since we already had this tool, we spent a few days making it more useful and capable for single package projects and require less overhead to configure it.&lt;/p&gt;
&lt;h2&gt;Simplified Setup&lt;/h2&gt;
&lt;p&gt;The configuration file goes into &lt;code&gt;//.config/mfgames-conventional-commit.json&lt;/code&gt; as usual, but setup has gotten significant easier. we added a &lt;code&gt;defaults&lt;/code&gt; section that applies to every project along with substitutions for packages with &lt;code&gt;{package}&lt;/code&gt; that allow each pack to have their own directories.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;$schema&amp;quot;: &amp;quot;https://mfgames.com/mfgames-conventional-commit-rs/schemas/v0.json&amp;quot;,
  &amp;quot;defaults&amp;quot;: {
    &amp;quot;tag_prefix&amp;quot;: &amp;quot;{package}-&amp;quot;,
    &amp;quot;directories&amp;quot;: { &amp;quot;include&amp;quot;: [&amp;quot;src/{package}&amp;quot;] },
    &amp;quot;files&amp;quot;: { &amp;quot;include&amp;quot;: [&amp;quot;src/{package}/**/*&amp;quot;] }
  },
  &amp;quot;packages&amp;quot;: {
    &amp;quot;MfGames.Cryptography&amp;quot;: {},
    &amp;quot;MfGames.DI.AutofacExtensions&amp;quot;: {},
    &amp;quot;MfGames.Gallium&amp;quot;: {},
    &amp;quot;MfGames.IO&amp;quot;: {},
    // There is more here
    &amp;quot;MfGames.ToolBuilder.Tables&amp;quot;: {}
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It used to be we had to have the &lt;code&gt;tag_prefix&lt;/code&gt; and &lt;code&gt;files&lt;/code&gt; elements in each one with a customization per project to include the path. Now, &lt;code&gt;{package}&lt;/code&gt; can be used to substitute the package name and defaults to apply to any package that doesn't have settings underneath itself. In the future, we'll have some configuration that will scan the &lt;code&gt;//src&lt;/code&gt; directory for the package names, but not this round.&lt;/p&gt;
&lt;h2&gt;Version Numbers&lt;/h2&gt;
&lt;p&gt;We also have two variants of version numbers based on usage. The first is the &amp;ldquo;flatten&amp;rdquo; version which calculates the most relevant major, minor, or patch version and applies it to the latest found tag. So if you have a &amp;ldquo;feat&amp;rdquo;, &amp;ldquo;fix&amp;rdquo;, &amp;ldquo;fix&amp;rdquo; on 1.0.0, it would be &amp;ldquo;1.1.0&amp;rdquo;. In contrast is the &amp;ldquo;detail&amp;rdquo; version which applies the version one at a time. So if you had the same &amp;ldquo;feat&amp;rdquo;, &amp;ldquo;fix&amp;rdquo;, &amp;ldquo;fix&amp;rdquo; on 1.0.0, the result would be &amp;ldquo;1.1.2&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Most software projects seem to make a conscious decision to bump up the version. With semantic releases, the versioning is done on the pipeline. For a long time, we used &amp;ldquo;flattened&amp;rdquo; as the basic approach since that is what &lt;a href="https://github.com/semantic-release/semantic-release#readme"&gt;Semantic Release&lt;/a&gt; did. However, with novels and the like, sometimes they go out without the pipeline running and it is useful to make sure new versions show up; hence the use of &amp;ldquo;detail&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This also resulted in a slightly different output for the &lt;code&gt;mfgames-conventional-commit version&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ cd src/MfGames.IO
$ mfgames-conventional-commit version
 package_name  last_version  flatten_version  flatten_changed  detail_version  detail_changed
 MfGames.IO    2.7.0         2.7.0            false            2.7.0           false
$ mfgames-conventional-commit version --all | head -n 5
 package_name                        last_version  flatten_version  flatten_changed  detail_version  detail_changed
 MfGames.Cryptography                14.7.0        14.7.0           false            14.7.0          false
 MfGames.DI.AutofacExtensions                      0.0.1            true             0.0.1           true
 MfGames.Gallium                     3.6.0         3.6.0            false            3.6.0           false
 MfGames.IO                          2.7.0         2.7.0            false            2.7.0           false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we're fond of changing format as needed, so it can also dump a JSON or Markdown version. And all logging is sent to stderr, so piping can work properly.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-sh"&gt;$ mfgames-conventional-commit version --json | jq
[
  {
    &amp;quot;package_name&amp;quot;: &amp;quot;MfGames.IO&amp;quot;,
    &amp;quot;last_version&amp;quot;: &amp;quot;2.7.0&amp;quot;,
    &amp;quot;flatten_version&amp;quot;: &amp;quot;2.7.0&amp;quot;,
    &amp;quot;flatten_changed&amp;quot;: false,
    &amp;quot;detail_version&amp;quot;: &amp;quot;2.7.0&amp;quot;,
    &amp;quot;detail_changed&amp;quot;: false
  }
]
$ mfgames-conventional-commit version --all --format markdown | head -n 5
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;package_name&lt;/th&gt;
&lt;th&gt;last_version&lt;/th&gt;
&lt;th&gt;flatten_version&lt;/th&gt;
&lt;th&gt;flatten_changed&lt;/th&gt;
&lt;th&gt;detail_version&lt;/th&gt;
&lt;th&gt;detail_changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MfGames.Cryptography&lt;/td&gt;
&lt;td&gt;14.7.0&lt;/td&gt;
&lt;td&gt;14.7.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;14.7.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MfGames.DI.AutofacExtensions&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0.0.1&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;0.0.1&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MfGames.Gallium&lt;/td&gt;
&lt;td&gt;3.6.0&lt;/td&gt;
&lt;td&gt;3.6.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;3.6.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MfGames.IO&lt;/td&gt;
&lt;td&gt;2.7.0&lt;/td&gt;
&lt;td&gt;2.7.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;2.7.0&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MfGames.Locking&lt;/td&gt;
&lt;td&gt;2.5.1&lt;/td&gt;
&lt;td&gt;2.5.1&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;2.5.1&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;p&gt;Another useful feature is the ability to perform updates if the version number changed. This is used to define an &lt;code&gt;updates&lt;/code&gt; element in the configuration file:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-json"&gt;{
  &amp;quot;$schema&amp;quot;: &amp;quot;https://mfgames.com/mfgames-conventional-commit-rs/schemas/v0.json&amp;quot;,
  &amp;quot;defaults&amp;quot;: {
    &amp;quot;updates&amp;quot;: [
      { &amp;quot;script&amp;quot;: &amp;quot;npm version {detail_version} --no-git-tag-version&amp;quot; },
      { &amp;quot;script&amp;quot;: &amp;quot;git tag v{detail_version}&amp;quot; }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see from above, the command will change the NPM version and create a Git tag. If &lt;code&gt;always_run&lt;/code&gt; is set to true, then it will run every time it is called, otherwise it will only run if the Git tag doesn't match the calculated tag.&lt;/p&gt;
&lt;p&gt;The main reason for this is because then we don't have to write a hundred plugins for different systems. You can just write the script to do it, call another program, or just write some output. Anything a shell can do, this can do.&lt;/p&gt;
&lt;h2&gt;Inferred Package&lt;/h2&gt;
&lt;p&gt;One of the more annoying things is that we couldn't figure out the package name from the current context. For the most part, that didn't mean much since we could always provide &lt;code&gt;--package package-name&lt;/code&gt;, but we found it useful enough that we made the tool figure out the patch from the &lt;code&gt;directories&lt;/code&gt; attribute above. This also handles &lt;code&gt;{package}&lt;/code&gt; substitutions, so it is generally useful.&lt;/p&gt;
&lt;p&gt;Both the &lt;code&gt;version&lt;/code&gt; and &lt;code&gt;update&lt;/code&gt; commands also take an &lt;code&gt;--all&lt;/code&gt; which means all packages in the project instead of &lt;code&gt;--package package-name&lt;/code&gt; for an arbitrary package, or either of these to guess it from the directory.&lt;/p&gt;
&lt;h2&gt;Sane Defaults&lt;/h2&gt;
&lt;p&gt;Finally, we added a sane default if the &lt;code&gt;//.config/mfgames-conventional-commit.json&lt;/code&gt; file is missing, or it doesn't have many settings. This assumes the entire directory is a single package called &amp;ldquo;default&amp;rdquo; and it uses a &amp;ldquo;vX.X.X&amp;rdquo; tag to represent versions.&lt;/p&gt;
&lt;h2&gt;Future Steps&lt;/h2&gt;
&lt;p&gt;This is a secondary project for us. Obvious, if we have an itch and it doesn't do something we want, we'll add it. If there are any &lt;a href="https://src.mfgames.com/mfgames-git/mfgames-conventional-commit-rs/issues"&gt;issues&lt;/a&gt; that would be really useful or if you come up with something useful for the project (that is in scope), report or comment on it and we'll see about implementing it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Introduction `@mfgames/nine-degrees-below` (Nine Degrees Below Palette)</title>
    <link rel="alternate" href="https://mfgames.com/blog/2025/06/26/introducing-nine-degrees-below/" />
    <updated>2025-06-26T05:00:00Z</updated>
    <id>https://mfgames.com/blog/2025/06/26/introducing-nine-degrees-below/</id>
    <category term="nine-degrees-below-palette" scheme="https://mfgames.com/categories/" label="Nine Degrees Below Palette" />
    <content type="html">&lt;p&gt;While working on &lt;a href="//fedran.com"&gt;Fedran&lt;/a&gt; covers, we've stumbled on Nine Degrees Below's &lt;a href="https://ninedegreesbelow.com/photography/gimp-srgb-lch-color-palettes.html"&gt;LCh color palette&lt;/a&gt; and fell in love with it. So much we wanted to use it for both the cover designs and the design on the website. This meant generating slightly differently arranged GPL palette files than what is provided on the link. We also needed CSS variables for our website.&lt;/p&gt;
&lt;p&gt;Along the way, things got more complicated and we ended up with this: a Node package that has &amp;ldquo;design tokens&amp;rdquo; as generated by &lt;a href="https://styledictionary.com/"&gt;Style Dictionary&lt;/a&gt;, CSS and Sass variable files suitable for using with Webpack and ESBuild, and a few other bits and bobs.&lt;/p&gt;
</content>
  </entry>
</feed>
