summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authort <t@tjp.lol>2026-07-11 23:19:23 -0600
committert <t@tjp.lol>2026-07-12 00:30:48 -0600
commit46a331dae8db622a491466ca9a0df3a5937ca80f (patch)
treeca769df8709cf47da2b728d6c7f2be4a0603b2ef
parent3f1234670b04161d211168a3af4bc85a1de99313 (diff)
back-port gemini capsule gemlog
-rw-r--r--.gitignore25
-rw-r--r--weblog/a-new-post-script.md30
-rw-r--r--weblog/advent-of-code.md15
-rw-r--r--weblog/aerc-surprised-me.md27
-rw-r--r--weblog/back-to-classics-the-picture-of-dorian-gray.md21
-rw-r--r--weblog/better-current-word-search-vim.md28
-rw-r--r--weblog/censorship.md23
-rw-r--r--weblog/dealerships-are-the-worst.md21
-rw-r--r--weblog/deplatforming.md22
-rw-r--r--weblog/from-packer-to-lazy-nvim.md45
-rw-r--r--weblog/geminis-killer-app.md19
-rw-r--r--weblog/gopher-item-types-a-gemini-miss.md37
-rw-r--r--weblog/guppy-v03.md93
-rw-r--r--weblog/happy-hacking.md39
-rw-r--r--weblog/jujutsu-is-the-real-deal.md49
-rw-r--r--weblog/leveling-up-the-toolkit.md209
-rw-r--r--weblog/missing-the-point.md45
-rw-r--r--weblog/neovim-lua.md19
-rw-r--r--weblog/nntp-a-great-protocol-for-bridging.md32
-rw-r--r--weblog/obligatory-first-post.md20
-rw-r--r--weblog/on-helicopter-parenting.md17
-rw-r--r--weblog/programming-language-learning.md25
-rw-r--r--weblog/runit-again.md33
-rw-r--r--weblog/the-baddies.md29
-rw-r--r--weblog/the-simple-joys-of-the-humble-slide-rule.md34
-rw-r--r--weblog/upcoming-gemini-projects.md46
26 files changed, 1003 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 17d36ea..eb1ad4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,5 +52,30 @@
/notes/sync/technologies/freebsd/pf.html
/pantograph/ds/pantograph-design-system-56a1c74e-41c1-486b-ba3c-4eabdfacef31/readme.html
/projects/index.html
+/weblog/a-new-post-script.html
+/weblog/advent-of-code.html
+/weblog/aerc-surprised-me.html
+/weblog/back-to-classics-the-picture-of-dorian-gray.html
+/weblog/better-current-word-search-vim.html
+/weblog/censorship.html
+/weblog/dealerships-are-the-worst.html
+/weblog/deplatforming.html
+/weblog/from-packer-to-lazy-nvim.html
+/weblog/geminis-killer-app.html
+/weblog/gopher-item-types-a-gemini-miss.html
+/weblog/guppy-v03.html
+/weblog/happy-hacking.html
/weblog/index.html
+/weblog/jujutsu-is-the-real-deal.html
+/weblog/leveling-up-the-toolkit.html
+/weblog/missing-the-point.html
+/weblog/neovim-lua.html
+/weblog/nntp-a-great-protocol-for-bridging.html
+/weblog/obligatory-first-post.html
+/weblog/on-helicopter-parenting.html
+/weblog/programming-language-learning.html
+/weblog/runit-again.html
+/weblog/the-baddies.html
+/weblog/the-simple-joys-of-the-humble-slide-rule.html
+/weblog/upcoming-gemini-projects.html
# END weft generated outputs
diff --git a/weblog/a-new-post-script.md b/weblog/a-new-post-script.md
new file mode 100644
index 0000000..c4ad068
--- /dev/null
+++ b/weblog/a-new-post-script.md
@@ -0,0 +1,30 @@
+---
+post_date: 2023-05-09
+---
+
+# A new-post script
+
+I got to take a new "newpost" script for a spin just now and was really pleased with it.
+
+It:
+- Moves the post file into my gemlog directory, renaming it in the process according to the date and the title, which it pulls from the first H1 in the post.
+- Adds a link to the post to my index/homepage. I bail out to python for this since it's a little heavier - more on that in a minute.
+- Alerts Antenna to the index page change. This is probably where 90% of the value comes in.
+
+## Mixing languages
+
+Now I'm feeling a little motivated to evict python from this script, because there's some friction in doing this task partly in bash and partly in python.
+
+Python isn't the greatest scripting language for one-liners, and using the -c switch isn't really useful any more beyond that. So for now this script (1) creates a tempfile, (2) dumps mostly hard-coded python into it (from a bash HEREDOC), (3) executes it, (4) captures the return code, (5) unconditionally removes the tempfile, and (6) exits the script if the return code was unsatisfactory. That's...not great.
+
+Although as I type this I guess I can probably execute "python" directly on a named pipe with bash `<(cat <<EOF ...)` syntax? That's worth a shot.
+
+EDIT: it was worth a shot, it works great.
+
+## Automating Antenna
+
+Where I *really* wanted to get with this script was something I could run which would unify (a) posting a new post, and (b) sending a request to antenna to alert it to the new post. If this isn't one-and-the-same step from a workflow standpoint, I will *definitely* forget to do the second part. And in the future, if more active aggregators in the Antenna style pop up, I can add them to the script once and have them integrated from there on out.
+
+Shoddy multi-language paradigm or not, it was a pretty big and important step to get *some* automation in place.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/advent-of-code.md b/weblog/advent-of-code.md
new file mode 100644
index 0000000..46ecdc3
--- /dev/null
+++ b/weblog/advent-of-code.md
@@ -0,0 +1,15 @@
+---
+post_date: 2023-12-11
+---
+
+# Advent of Code
+
+AoC '23 is off to a great start.
+
+The problems are good ones and lots of fun, but it definitely feels like less of a clean ramp up in difficulty than last year's.
+
+I've been collecting my solutions in this gemini capsule, along with those of anyone on this pubnix who wish to participate.
+
+[Ctrl-C.Club Advent of Code aggregated solutions](gemini://gemini.ctrl-c.club/~tjp/p/aoc23/)
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/aerc-surprised-me.md b/weblog/aerc-surprised-me.md
new file mode 100644
index 0000000..b84cc42
--- /dev/null
+++ b/weblog/aerc-surprised-me.md
@@ -0,0 +1,27 @@
+---
+post_date: 2024-08-24
+---
+
+# Aerc surprised me
+
+I've never been particularly happy with CLI or TUI email options. Aerc might just finally change that.
+
+My TUI email journey has basically been: every couple of years, try and get a \[neo\]mutt setup I'm happy with, only to fall back to in-browser gmail after a few months at the most, when something in the rube-goldberg machine I've invariably constructed, breaks.
+
+Some time in the late oughts I tried sup, a promising try built in ruby and which sported gmail-like threading by default. But it was slow, and never really did well with the HTML email that's just a constant of modern (and even late-oughts) life.
+
+I've done the offlineimap dance, which became the isync dance, tried various lightweight/standalone SMTP senders like msmtp. These days the whole story is much better because of neomutt: it supports IMAP and SMTP directly, has grown native threading/tree-view support, and comes pre-baked with the half-dozen or so patches I used to always have to cobble together. But HTML email is still a pain, and gmail's crazy IMAP folders are still a pain, and the interface asking so much of the user at all times is still a pain.
+
+---
+
+Aerc represents a much-needed fresh reboot, from a craftsman who clearly has a software worldview very similar to my own. It's highly extensible and powerful, but unlike long-in-the-tooth mutt, achieves this power under a self-imposed simplicity constraint. There are multiple configuration files, each one employing a simple INI-style format. There are keybindings, all of which are changable, and all of which are built out of commands in the program's ex-mode.
+
+On top of the program itself, aerc ships with a directory of filter scripts, utilizing w3m to convert HTML email parts in the terminal, or using the system's native per-mimetype defaults to open them in an external program.
+
+With these scripts and the structure of the program itself (modal, command-based, unix-oriented), and some really smart inventions like built-in folder name remapping, aerc satisfies the itch for a good old-timey UNIX program, while simultaneously anticipating the needs of an email client in the 2020s.
+
+I'm loving it.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
+
+> I also still use aerc.
diff --git a/weblog/back-to-classics-the-picture-of-dorian-gray.md b/weblog/back-to-classics-the-picture-of-dorian-gray.md
new file mode 100644
index 0000000..f8053a9
--- /dev/null
+++ b/weblog/back-to-classics-the-picture-of-dorian-gray.md
@@ -0,0 +1,21 @@
+---
+post_date: 2023-09-04
+---
+
+# Back to classics: The Picture of Dorian Gray
+
+Every once in a while I like to pull out an old classic book.
+
+(I only really read a book "every once in a while" though, so this is a pretty large portion of my overall reading).
+
+I'm also rather excited to start tracking my reading here, and hopeful I'll read more as a result. Readability has been dead to me for a long time, and lately bookwyrm wasn't cutting it either.
+
+The old masters of English literature are considered so for a reason, and I'm always pleasantly surprised by how much I enjoy them.
+
+I was already aware of the plot of The Picture of Dorian Gray, but I don't believe I ever actually read it in school. At this point I'm just a chapter in, but the writing absolutely sucks you in. It might even be better to already know of the overall plot ahead of time, because Wilde is already foreshadowing the major plot themes as he first introduces characters.
+
+And the characters! The dialogue forms these characters into more clear, distinct, and full humans than what most authors ever pull off. They are at once varying levels of clever, optimistic/pessimistic, artistic, and have dramatically different motivations. I usually find that an author's characters are (probably) just reflecting different aspects of the author's own character, and are thus limited to being less than a full person, lest they overlap too much with other characters. I don't know how Wilde conceives of these characters and so effortlessly gives them complete voices of their own, but he does.
+
+I'm thoroughly enjoying this, and even just having barely cracked it open, I can heartily recommend it.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/better-current-word-search-vim.md b/weblog/better-current-word-search-vim.md
new file mode 100644
index 0000000..fa06126
--- /dev/null
+++ b/weblog/better-current-word-search-vim.md
@@ -0,0 +1,28 @@
+---
+post_date: 2022-07-03
+---
+
+# Better current word search in vim
+
+I noticed something when I was refactoring my vim configs for the Nth time last week, which [I wrote about in a bit more detail here](./neovim-lua.md). It's a bit of vim configuration that I've been copying forward without thinking about it for years and years, never even remotely considering pruning it because it's so useful. I also came up with it myself but havne't ever really put any effort into sharing it, so here we are.
+
+In just a few lines of lua, it's this:
+
+```lua
+vim.keymap.set('n', '*', function()
+ vim.api.nvim_exec('/\\v<' .. vim.fn.expand('<cword>') .. '>', false)
+end)
+vim.keymap.set('n', '#', function()
+ vim.api.nvim_exec('?\\v<' .. vim.fn.expand('<cword>') .. '>', false)
+end)
+```
+
+By default, `*` and `#` do something very similar, they search for the word currently under the cursor. `#` simply searches backwards, while `*` is a forward search.
+
+The mappings above copy that behavior (perform it as you would do it directly, explicitly) but introduce a very useful slight tweak. Executing `/expand(<cword>)` simply searches (`/`) for the word currently under the cursor (`<cword>`). But introducing `<` and `>` around it places word boundaries in the search string.
+
+You see by the default naive cword search, any larger words which fully contain your search term would also show up as results. Try searching for a single letter variable that way and you'll have a bad time.
+
+I think of this as a fantastic example of the utility of programmatic configuration (the sort that's made all the more powerful by using lua over VimL) - you're free to make the editor better in a hundred minor ways to smooth out your editing workflows, and in a way which is itself very much like maintaining code: build yourself a useful abstraction and don't have to think about it too much any more.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/censorship.md b/weblog/censorship.md
new file mode 100644
index 0000000..7553f62
--- /dev/null
+++ b/weblog/censorship.md
@@ -0,0 +1,23 @@
+---
+post_date: 2024-01-03
+---
+
+# "censorship"
+
+A domain was blocked from Antenna after a political post was made. That sounds bad.
+
+- [The post in question](gemini://gem.librehacker.com/gemlog/your-word/20240101-0.gmi)
+- [Follow-up upon learning of the ban](gemini://gem.librehacker.com/gemlog/your-word/20240102-0.gmi)
+- [Another follow-up saying the same thing](gemini://gem.librehacker.com/gemlog/starlog/20240103-0.gmi)
+
+I was a little shocked when I first encountered this yesterday, starting with the second link.
+
+Then I read the original post and my shock transferred entirely elsewhere.
+
+> Hamas and the Palestinians are essentially the same thing. The Palestinians are trained from infancy to hate Jews and to believe that dying in the act of murdering Jews is the height of glory and achievement.
+
+Besides being a lie, this is an utterly despicable, de-humanizing, and genocide-cheering statement. There are few who would dispute the validity of Israel's stated goal of eradicating Hamas. Equating Hamas with all Palestinians is therefore an explicit call to wipe out an ethnic people group.
+
+Look, I think the bar should be _really_ high before we go banning someone from public discourse channels. My dude cleared it and then some.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/dealerships-are-the-worst.md b/weblog/dealerships-are-the-worst.md
new file mode 100644
index 0000000..bda1255
--- /dev/null
+++ b/weblog/dealerships-are-the-worst.md
@@ -0,0 +1,21 @@
+---
+post_date: 2023-11-03
+---
+
+# Dealerships are the worst
+
+My Honda is great. All dealerships are _incredibly_ bad by comparison.
+
+Yes, I know: the sky is also still blue and water is still wet.
+
+Here's a short story.
+
+I started getting the warning light that my key fob's battery was getting low. I look at the thing and it's not obvious how to open it, so I assume it's the usual auto industry BS of some proprietary opening device and probably a specialized battery for no reason other than to capture all the business of replacing them.
+
+So I call the nearest Honda dealership. Of course, if I had any confidence that I could just walk in to the place and talk to a mechanic I'm sure it'd be a 3 minute interaction: they get out their specialized opening tool, pop in a new proprietary Honda battery, and I pay and go on my way. But instead I'm spending 10 minutes on hold for the service department, which just transfers me to the parts department where I spend another 5 minutes on hold before giving up. I try calling the next closest and spend another 10 minutes on hold before giving up there as well.
+
+Finally I figure I'll just check YouTube university and immediately find a 2 minute video that tears my original assumption to shreds. There's a super easy way to open the key fob, with some small plastic tabs even intentionally built in to make this easier. Then it's just a CR2032 battery cell inside: common, cheap, and available at the local grocery store. I've made the trip to the grocery store and replaced the battery in less time than I spent on hold trying to just make the appointment.
+
+This is kind of what Honda is known for, and I should have expected it from the get-go: inexpensive, super practical, user-focused design choices. CRVs used to come with a small folding table stored above the spare tire! I find the disparity so jarring between the car manufacturer and the physical locations that represent their brand to consumers.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/deplatforming.md b/weblog/deplatforming.md
new file mode 100644
index 0000000..ff42aa2
--- /dev/null
+++ b/weblog/deplatforming.md
@@ -0,0 +1,22 @@
+---
+post_date: 2024-01-04
+---
+
+# deplatforming
+
+We've really got to raise the bar in talking about issues of free speech in small web spaces, myself included.
+
+In my post yesterday I barely alluded to "censorship" being a misnomer for what happened on Antenna (simply with the quotes in the title) but neglected to point it out properly in the post body.
+
+Others have now pointed this out, but if someone makes a post on their own gemini server, and that post remains on their own gemini server, censorship has not occurred. Instead, a _different_ gemini server declined to host links to that post. In it's capacity as a link aggregator you could stretch to call Antenna a "platform", and thus we get to the more appropriate description of what happened here: deplatforming.
+
+- [gemini://oberdada.pollux.casa/gemlog/2024-01-04_censorship.gmi](gemini://oberdada.pollux.casa/gemlog/2024-01-04_censorship.gmi)
+- [gemini://lesogorov.site/glogs/2401/Censorship.gmi](gemini://lesogorov.site/glogs/2401/Censorship.gmi)
+
+Happily there seems to be agreement that the operator of Antenna has the right to do whatever they want with their own site. Yet somehow there also seems to be a strong streak of what appears pretty close to free speech absolutism. The notion is that free speech implies that you have to hear all kinds of things you don't like, and the converse would imply that you wouldn't be able to express any ideas that others don't like.
+
+But there is a massive gulf between "I don't like this" and "this is dangerous". It's 2024 and we have a pretty solid understanding of where the natural echo chambers of our self-selected online communities lead. There's no need to chalk up this ban action to "the mod doesn't like this speech" when an equal or better explanation is "the mod doesn't want their platform to be a place where people can be radicalized".
+
+Free speech must have limits. If calls for genocide (explicit or implied) don't reach your limit, I submit that your position is intellectually lazy.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/from-packer-to-lazy-nvim.md b/weblog/from-packer-to-lazy-nvim.md
new file mode 100644
index 0000000..9ab0c95
--- /dev/null
+++ b/weblog/from-packer-to-lazy-nvim.md
@@ -0,0 +1,45 @@
+---
+post_date: 2023-11-01
+---
+
+# From packer to lazy.nvim
+
+I ported my vim configuration again.
+
+This time I swapped out the plugin manager from packer to lazy.nvim. I'm certainly wary of spending too much time on productivity fine-tuning, but since this took a few hours in total and it's been well over a year since the last refactor, I'm okay with it.
+
+[porting my longstanding vimscript configuration to lua](./neovim-lua.md)
+
+## Plugin management modularization
+
+In my opinion, the biggest improvement of neovim over vim is to the plugin API. Introducing lua, and providing solid lua APIs to the native treesitter and LSP capabilities has completely changed what's possible for plugin authors. And the predictable result of that change is that modern neovim tends towards a base system with plugins providing more and more central functionality.
+
+So even though I broke apart my configuration into separate lua modules with the vimscript->lua transition, lately the vast majority of the configuration lived in the single lua file that was managing packer and my plugins. Lazy.nvim includes some utility functions to merge lua tables (which act as the language's arrays and hash maps) together, and builds on that to support breaking apart your plugin configuration into separate modules to be merged by the system.
+
+You have a good deal of flexibility with how you can choose to go about this. One file per plugin is certainly possible, but I've opted to use files/modules for logical topics. So, for example, the configuration of all LSP-related modules lives together. There's another for color themes, and another for treesitter, etc. Some of these files contain only one plugin, but they don't have to.
+
+This is the largest practical benefit of this refactor.
+
+## Nvim-native UI
+
+The initial impetus for selecting lazy.nvim was actually this: I have been blown away by mason.nvim's UI for managing LSP servers, and lazy.nvim offers something very similar for plugins. I really hope to see more copycats of this paradigm (in particular I'd love to see one for treesitter integrations).
+
+## Portable neovim configuration
+
+Mason and lazy.nvim also have this in common: they place the actual assets they manage in `~/.local/share/nvim`. What's really important here is that it's not anywhere in `~/.config/nvim`, so that directory is entirely hand-written configuration. All this means that (a) the plugins themselves and any support binaries will live elsewhere, and (b) that "elsewhere" can be entirely re-created from the configs using lazy and mason. So what I can do now (and have recently done) on remote machines I manage is:
+
+```
+$ ssh myuser@somehost rm -rf .config/nvim .local/share/nvim && scp -r .config/nvim myuser@somehost:.config/nvim
+```
+
+First completely clear any neovim configuration and assets on the remote machine, then recursively copy up my whole configuration directory. It'll be a small copy because it's just my hand-written lua files (no fzf binary or plugin git repos). The next time I load up neovim on that remote machine, it'll bootstrap lazy.nvim, download all the plugins, and initialize everything.
+
+## What's missing
+
+All in all, the new setup on lazy.nvim is superior to what I had with packer in every way. So when I think of something I'd love to see in the future, don't read that as something I miss from the old setup :P
+
+Mason is managing a limited set of LSP options so everything is right there - what you've installed and what you haven't. Lazy.nvim is different because the set of vim plugins you haven't installed is pretty much unknowable. However, it is telling that strings like 'pluginname' automatically reference vim.org/scripts, and strings like 'author/name' automatically reference github.com. With that in mind, it would be great if the Lazy UI offered a means of searching vim.org scripts and public github repos. There would probably be github rate limits to contend with, but otherwise it seems like something that should be doable.
+
+Finally, as I mentioned before it would be amazing to have a UI like Mason but which manages treesitter parsers. I wouldn't expect this to come from lazy.nvim, it's just another plugin that should exist.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/geminis-killer-app.md b/weblog/geminis-killer-app.md
new file mode 100644
index 0000000..a47533f
--- /dev/null
+++ b/weblog/geminis-killer-app.md
@@ -0,0 +1,19 @@
+---
+post_date: 2023-05-09
+---
+
+# Gemini's Killer App
+
+[Joel of innerteapot.com wrote about Gemini's focus on text.](gemini://innerteapot.com/gemlog/20230509-geminis-killer-app.gmi)
+
+To perhaps get bogged down in the technical details, I think Gemini owes a lot of this to its thoughtful design. Specifically here by not having allegories for CSS or Javascript. All that's left at that point is the text. There are no design or interactivity knobs to tune to make up for poor writing.
+
+But I believe it's bigger than that. Gemini's design eschews extensibility, and a lack of styling options or a client-side programming language are just 2 examples of this larger choice.
+
+Gemini will also never be attractive to corporate interests because it will never have cookies or any other tracking mechanism. Why is that? Because there are no headers in the format, and so nowhere to put the tokens for such a mechanism. This is where the web really truly separated from gopher, BBSes, and everything else that came before it: when you have support for arbitrary key/value pairs people can and will build anything. At that point it's a platform. Corporations love platforms.
+
+If Gemini successfully holds off capitalism, the only motive for publishing into geminispace is passion. This is the real killer app: nobody has a job writing for Gemini. It's all passion, because Gemini doesn't allow the incentive structures to support anything else.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/gopher-item-types-a-gemini-miss.md b/weblog/gopher-item-types-a-gemini-miss.md
new file mode 100644
index 0000000..4f76e2f
--- /dev/null
+++ b/weblog/gopher-item-types-a-gemini-miss.md
@@ -0,0 +1,37 @@
+---
+post_date: 2023-09-04
+---
+
+# Gopher item types: A Gemini miss?
+
+Hardly new, I know - a geminaut questions a design decision in the protocol.
+
+I came to the small web a little backwards. I discovered Gemini via the tildeverse a little over a year ago, quickly created this ~/public_gemini directory and got posting, responding to others' posts, and finding their responses on [Cosmos](gemini://skyjake.fi/~Cosmos/index.gmi).
+
+It was only after reading about [Gemini's philosophy and creation back-story](gemini://gemini.circumlunar.space/history/) that I started to learn about Gopher more deeply. Now, having written a few Gemini and Gopher servers and spent some time in both protocols, I question one design choice in Gemini being strictly _more powerful_ than gopher: the lack of anything performing the role of item types.
+
+Don't get me wrong: gopher's item types are a pain for implementors and a source of confusion for the unintiated ("what's with that magical /1/... path prefix?"). They certainly don't pass muster as a modern, simple, clean design. BUT one of the real joys of navigating around is the little indicators you get of what you're about to get yourself into with a link:
+
+```Sample output from gopher.floodgap.com as rendered in Bombadillo
+ --- Getting started with Gopher -------------------------------
+MAP [3] Getting started with gopher, software, more
+ (what is Gopherspace? We tell you! And find out how
+ to create your own Gopher world!)
+
+TXT [4] Using your web browser to explore Gopherspace
+ (READ IT! LEARN IT! LOVE IT!)
+ (useful tips for gopher newbies, updated 17 November 2018)
+
+MAP [5] The Overbite Project: Gopher clients for mobile and desktop (OverbiteWX, OverbiteNX,
+ Overbite Android)
+ (download gopher add-ons for Mozilla Firefox/SeaMonkey,
+ mobile clients for Android and more! Put Gopherspace on
+ your mobile phone or desktop computer!)
+MAP [6] Other Gopher clients for various platforms
+```
+
+Will this link take me to another gopher menu? Show me a text file? Render an image? Download a binary file? Bounce me over to WWW? It's rather fantastic to get the visual cues that clients provide, and that's made possible by the information being embedded in the link itself in the underlying protocol.
+
+I miss that clarity when browsing gemini.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/guppy-v03.md b/weblog/guppy-v03.md
new file mode 100644
index 0000000..068fa25
--- /dev/null
+++ b/weblog/guppy-v03.md
@@ -0,0 +1,93 @@
+---
+post_date: 2023-10-25
+---
+
+# Guppy v0.3
+
+It feels like there's a renaissance of text client/server protocols in the small web these days, and I love seeing these come out.
+
+[dimkr posted a v0.3 draft spec for a new text protocol named "guppy"](gemini://gemini.dimakrasner.com/guppy-v0.3.gmi)
+
+## The skinny
+
+The stated goal of the spec is being easy to implement even for hosting servers on microcontrollers such as the Pi Pico W, going more barebones than Spartan in the process.
+
+The largest deviation from similar recent work is eschewing TCP in favor of UDP and implementing out-of-order resolution and retransmission in the application layer. But the spec and implementation complexity arising from this decision are very much at odds with the goals.
+
+## Guppy spec TL;DR
+
+Probably the quickest way to get up to speed on this spec is in terms of other small web protocols.
+
+Spartan simplified Gemini by removing TLS from the picture, but complicated it by adding an additional line type to gemtext with request/upload implications. Guppy also declines to encrypt content, and adopts the original form of gemtext without the Spartan := line type.
+
+Like other modern-but-small-web protocols, Guppy adopts a simple single-line CRLF-terminated request type, and a small number of response types differentiated by a leading numeric type. Though any number 2-32767 represents success, and doubles as a starting sequence number.
+
+Then it also adds on additional message types necessary for adding order and retransmission given the choice of UDP transport: continuations, EOFs, and acks. Continuations increment the sequence/identification number of the packet they are continuing and carry additional payload, EOFs look just like an empty continuation, and acks simply echo back a sequence number. Requests and acks are sent by the client, while success/redirect/error responses, continuations, and EOFs are sent by the server (response messages serve as acknowledgement to the client that a request was received).
+
+## Some problems
+
+A bunch of things jumped out at me in the spec immediately.
+
+### Concurrent packets
+
+In the section "Packet Order" we have this:
+
+> Servers should transmit multiple packets at once, instead of waiting for the client to acknolwedge a packet before sending the next one.
+
+but later, the section on acknowledgement messages directly contradicts this using the same "should" language:
+
+> The server should wait for the client to acknowledge the previous chunk of the response (the success packet or the previous continuation packet) before sending the next continuation packet, to avoid waste of network bandwidth.
+
+It seems like this probably was changed but the acknowledgement section wasn't updated. Or possibly the other way around, but the "Packet Order" version is more pervasive in the spec.
+
+### Packet sizes
+
+32767 immediately catches the eye of anybody who has written a little bit-twiddling C before. It's the maximum value of a signed 16-bit number. I'd suggest that there are readers who will skim this and assume that a signed 16 bits is enough to fit the sequence number (particularly among readers who are writing software for microcontrollers).
+
+But that initial response sequence number can (and generally, will) be incremented by subsequent continuation and end-of-file packets. So this is a danger that is probably worth calling out. Note also that with the spec-provided 512 byte minimum chunk size, storing the sequence number in an unsigned 16-bit number caps the guaranteed download size at 16MB. Some conversation of the interplay between sequence number integer sizes, chunk sizes, and file download sizes may be in order.
+
+### Re-transmission
+
+The section on acknowledgement messages ends with this:
+
+> The client may attempt re-transmission of an acknowledgement packet
+
+A client won't generally know whether an acknowledgement packet was received by the server because there are no acks of acks in guppy.
+
+One possible (but not guaranteed) ack failure indication would be receiving a re-transmission of an already-acked packet, but this is something the spec elsewhere suggests clients ignore, and is a pretty awkward heuristic to code.
+
+### Out-of-order packets
+
+The spec on continuation packets:
+
+> The client must ignore packets where the sequence number is not the sequence number of the previous packet plus 1.
+
+This contradicts just about everything said elsewhere about out-of-order packet handling so it probably just wasn't updated in some prior iteration.
+
+### Duplicate requests
+
+On request packets:
+
+> The client may attempt re-transmission of a request packet if no response is received after a while and the server must ignore duplicate request packets.
+
+The server won't be able to distinguish re-transmission of the "same" request packet from a legitimate re-request of the same page, perhaps from a client with a refresh button. These are indistinguishable because request packets don't have a sequence number.
+
+## A theme emerges
+
+I've omitted some other more nit-picky things, partially because they are more minor, and partially because the above list really highlights that the use of UDP is overall a huge problem in the guppy spec. It contributes the spec complexity that led to some mistakes, and it creates protocol complexity that I think this spec under-appreciates, leading to ambiguity and complications for would-be implementors. Every last issue in the above list would be resolved by adopting TCP and stripping out the parts of the spec that deal with rebuilding it's capabilities (which I expect would constitute a majority of it).
+
+It's a classic mistake to look at complicated machinery like TCP and assume it's bloated. But the effort here to provide a simpler alternative only misses _required complexity_, because it was an under-appreciation of the complexity inherent in the problem itself that led to the assumption in the first place.
+
+Even though TCP contains a more complicated and convoluted solution to the problems of re-ordering and re-transmission, its use would be a massive simplification both for this spec and especially for implementors.
+
+## The long way out
+
+But, to each their own! Don't let me dissuade you from tackling these problems on top of UDP if it's what you really want to do. It's certainly an interesting area, and in that case I hope the issues identified above are helpful in fleshing out a comprehensive solution. It's just that what you're doing at that point is not simplification.
+
+Here are a few suggestions, informed by existing implementations (like TCP), which can help with some of the problems I noticed.
+
+- Split apart message type and sequence number. You get the rest of the possible message types as room for expansion. Also it makes the next suggestion work.
+- Precisely define the size of sequence numbers, and allow them to wrap around. Any number in the range can now be chosen as the starting point, and incrementing from the maximum value gets you to zero. Now there's no arbitrary limit to your total message size.
+- Don't bother retrying acks. If the server doesn't get one it'll retransmit its packet, and the client now *should* ack duplicate packets that it receives.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/happy-hacking.md b/weblog/happy-hacking.md
new file mode 100644
index 0000000..2cb0cad
--- /dev/null
+++ b/weblog/happy-hacking.md
@@ -0,0 +1,39 @@
+---
+post_date: 2023-01-10
+---
+
+# Happy hacking
+
+Winter has been a great time for "for me" coding projects.
+
+Holiday breaks, and having successfully managed myself out of many of my day-to-day responsibilities at my job have left a good deal of time for self-driven projects. Like this gemlog!
+
+## AoC 2022
+
+I did advent of code 2022. Well, most of it. I'm currently on part 2 of day 17, and finally acknowledging that I won't be finishing. Almost. Probably.
+
+After doing a bunch of them in my usual langauges of Go and Python, I did [one in zig](https://ziglang.org/) which I'd been hoping to get deeper into. It was far harder and slower because of the new-to-me and lower level language, but I was learning so much so fast that I committed to doing the rest of the project in only zig. I didn't switch away, but I didn't finish either.
+
+There are some really interesting challenges in jumping to a manual memory management language like zig. This was a ton of fun, and I highly recommend taking advent of code as an opportunity to learn a new langauge really well, but there are a few things I would have done differently:
+- Start with the new language from the beginning. Advent of code has a gradual ramp up, where the first days are the easiest.
+- Find a friend to do it with.
+
+## Gus
+
+Just in the last few days I've written [a server toolkit for gemini called "gus"](https://tildegit.org/tjp/gus) (after Virgil "Gus" Grissom, one of the Gemini pilots and commander of Gemini 3). It's intended to be the net/http of Gemini: a well designed, highly composable set of libraries for interacting with Gemini from Go.
+
+There's a ton to do still before I'd make a release, but I'm happy enough with where it is now that the next order of business is to write up a release announcement and start pushing it on IRC, fediverse, mailing lists, and of course gemini.
+
+## What's next
+
+The interesting part about my hobby hacking in 2023 is not the specific projects I'd like to work on, but the life change that will enable it. I'm stepping back from my full-time role at my company to more of an advisory position with a very small time commitment.
+
+I'm in this incredibly blessed position of having a spouse whose salary can nearly keep up with our family expenses and a financial situation that will let me comfortably recuperate for a year or more from the burn out that started forming in my work. I can enable her to invest more in her career, I can spend more quality time with God, kids, dog, nature, hobbies, and generally doing things that actually grow and edify me. Part of that will definitely be stepping up on minor code projects I enjoy.
+
+I definitely want to keep investing in gus, and I'm excited about the little communities I've found in gemini/small web, and here on ctrl-c.club as well.
+
+I hope you may also find the time and mental and emotional energy to pursue things that fill you up in 2023!
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/jujutsu-is-the-real-deal.md b/weblog/jujutsu-is-the-real-deal.md
new file mode 100644
index 0000000..83ba324
--- /dev/null
+++ b/weblog/jujutsu-is-the-real-deal.md
@@ -0,0 +1,49 @@
+---
+post_date: 2025-03-22
+---
+
+# Jujutsu is the Real Deal
+
+An experience report from using jj as my primary git frontend for a few months.
+
+I'm one of those older generation of software engineers who tends to view the new weekly tech fads with a bit of suspicion - not exactly quick to jump on the next bandwagon. But after seeing the new Jujutsu VCS pop up on the lobste.rs homepage a bunch of times over a few weeks, I had to at least investigate.
+
+Jujutsu is for sure an improvement on git. It includes a simpler, more DAG-forward data model, commands that push you towards a better workflow, and dramatically better conflict resolution. But what really sets it apart from the numerous VCSes that can be said to be "better" than git, is the upgrade path to begin using it.
+
+## The data model
+
+JJ does include an immutable, checkpoint-oriented "state of the whole tree" object like git commits (we'll talk more about how alike they are later), but those aren't the primary objects you work with. Wrapping a sequence of those commits, the main object is a jj "change". The sequence of commits don't point to one another as a linked sequence of commits, but rather look more like the result of repeatedly performing "git commit --amend", where each subsequent commit replaces the one that came before.
+
+The star of the show is jj's change object. This is where JJ's DAG lives, and while you can create "bookmarks" which behave like git branches, you generally don't for local development but rather just keep an eye on the actual full DAG of changes and operate on that. It's much clearer to operate this way because the "jj log" command behaves like "git log --graph" by default, always surfacing the graph for you - and "jj log" is what you get by default by just running "jj".
+
+I've been the resident "git expert" on teams a few times before, and from being called in to clean up git messes dozens of times, I can say with confidence that this DAG-centric view of the world will resolve 90% of the ways people mess themselves up in git. While git does have a graph of commits under the hood, and branches are "merely" movable pointers to commits in that graph, the available commands and mental model that they push leads people to primarily thinking in terms of linear branches. JJ doesn't make this mistake.
+
+But probably the biggest killer feature of jj changes is that they are used far more broadly than git commits. JJ has no "index", rather it always keeps your "current commit" up to date with whatever is in the working tree. This also removes the idea of "unstaged changes". It's as though your every invocation of "git" was prefixed with "git commit -a --amend; ". You never have to stash anything, because "jj edit" which moves your "current commit/change" to another change will always immortalize your current working tree in the change you are leaving. And it lets the command set focus *just* on jj changes, rather than the many other states of changes that exist in git.
+
+## The workflow(s)
+
+Another broken part of git's model that jj's changes fix is the dual nature of git's commits. On the one hand they represent any savepoints/checkpoints that you want to make before working on another feature on another branch, or look at someone else's diff in your working tree, or whatever. But those frequent savepoints get really cluttered - git users are familiar with the frequent commit descriptions of "fixes" and similar. So often git users end up wrapping up a feature by spending some time in git commands to rebase/clean up their history - merging the long string of frequent savepoints into a smaller number of clear "complete a feature" commits with nice properties like taking the build from green to green. Others skip that step entirely and perhaps give up on the idea of ever being able to meaningfully revert a feature with git commands.
+
+So which is it? Is a git commit this transient "quick save" of the working tree state, or a final artifact that provides an actual useful history? It's both, and it takes some extra work on the part of the user to accomplish both tasks well.
+
+JJ's changes, as mentioned before, include a sequence of commits. JJ really eagerly amends to the latest commit of the current change - these are your frequent save points. The wrapping change is what holds the "description" (allegory for git commit messages), and will be useful as the final historical artifact. You can even write the change description up front before starting the work, and then get going on the sub-iterations towards that stated goal.
+
+I've noticed that lots of more sophisticated git users tend towards frequently amending to their current commit, as a way of splitting the difference between these meanings of a git commit. You can view JJ as a really simple automation of this workflow (except that it gives you tools to go back to an earlier revision of any change).
+
+In the absence of an "index" a la git's, jj users tend to create a change on top of the one they're building up as a final historical change, and have that as the frequently-amended "current change". Then a periodic "jj squash" will *move* changes from the current commit into its parent - this is the replacement for staging in git's index and then flushing into a commit, but because in jj it's all changes, you end up with these commands being useful for other repository surgery between existing changes.
+
+These commands being so good at operating on changes also feeds the strength of the earlier point about not needing branches. You see the DAG of changes with "jj log", and can operate on them (rebase individual changes or whole subtrees, move diffs around between changes, etc) with a powerful toolkit.
+
+## Conflict management is utterly revolutionized
+
+In jj, conflicts are actually included in the data model. This means you can perform a rebase, find that it results in a conflict, and put off dealing with it until later - in the meantime you want to jump over to a different change and work on something else. You can continue from where you left off later and fix the conflict directly, or perhaps even resolve it by performing another rebase. That sinking feeling you get using git when a conflict shows up, and knowing that you're going to have to deal with this before you can *do anything else* with git, is completely gone.
+
+## The real magic trick: the path to getting started using it
+
+All this is great, but plenty of later VCSes can claim to be better than git in these and other ways, but we aren't using those either because we have to work on teams, and git is completely dominant. Jujutsu lets you have your cake and eat it too. It's the first VCS that I'm aware of with pluggable storage back-ends, the first (publicly available) of which is, in fact, git itself. Currently, every jujutsu repository wraps a git repository, which you can use to work together with others who don't even have to know that you're using jj and not git directly.
+
+So when I said "You can view JJ as a really simple automation of this \[git\] workflow", I *really* meant that. This is also why the revision objects within jj's changes are so much like git's commits - they actually are git commits. I've been using jj now for about 6 weeks as my *only* VCS tool, and have continued uninterrupted working with a team that uses git and github. There's really no reason not to give it a go.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/leveling-up-the-toolkit.md b/weblog/leveling-up-the-toolkit.md
new file mode 100644
index 0000000..254ddcf
--- /dev/null
+++ b/weblog/leveling-up-the-toolkit.md
@@ -0,0 +1,209 @@
+---
+post_date: 2024-11-20
+---
+
+# Leveling up the toolkit
+
+A story of exploration, optimization, and how awesome open-source tooling can be.
+
+I'm somewhat stuck using a mac because of multiple critical tools that I use on it. I've always been a DIYer with my tools though, which is something FOSS OS's like Linux or *BSD enable much more. But I've found two tools that I enjoy a great deal: Aerospace (a tiling window manager in the vein of i3) and sketchybar (a status bar like i3bar or polybar). Both of these tools offer fantastic configurability and do an admirable job piercing the walled-garden that is the Mac OS.
+
+Because a desktop status bar generally displays the list of workspaces and indicates which one is active, there's a good deal of integration needed between the two. I got started with the [suggestion in the Aerospace wiki](https://nikitabobko.github.io/AeroSpace/goodness#show-aerospace-workspaces-in-sketchybar).
+
+## The starting point: documented suggestions
+
+First, the instructions have us configure aerospace to (a) start up sketchybar after it starts up itself, and (b) trigger a custom sketchybar event whenever the active workspace changes.
+
+```
+# ~/.aerospace.toml
+after-startup-command = ['exec-and-forget sketchybar']
+
+exec-on-workspace-change = ['/bin/bash', '-c',
+ 'sketchybar --trigger aerospace_workspace_change FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE'
+]
+```
+
+Next, in sketchybar's configuration file (which is just a shell script - beautiful) we'll both create the custom event for aerospace to trigger, and loop over aerospace's workspaces to display them in the status bar. These status bar items will also listen for the custom event, and trigger a script in the sketchybar config's plugin directory.
+
+```
+# ~/.config/sketchybar/sketchybarrc
+sketchybar --add event aerospace_workspace_change
+
+for sid in $(aerospace list-workspaces --all); do
+ sketchybar --add item space.$sid left \
+ --subscribe space.$sid aerospace_workspace_change \
+ --set space.$sid \
+ background.color=0x44ffffff \
+ background.corner_radius=5 \
+ background.height=20 \
+ background.drawing=off \
+ label="$sid" \
+ click_script="aerospace workspace $sid" \
+ script="$CONFIG_DIR/plugins/aerospace.sh $sid"
+done
+```
+
+The "click_script" in there is a nice touch, it's having aerospace change the active workspace when we click one in the status bar.
+
+Finally, we'll create that script in the plugin directory. It's ultimately what's going to be run whenever there's a change to the currently-active workspace.
+
+```
+# ~/.config/sketchybar/plugins/aerospace.sh
+#!/usr/bin/env bash
+
+if [ "$1" = "$FOCUSED_WORKSPACE" ]; then
+ sketchybar --set $NAME background.drawing=on
+else
+ sketchybar --set $NAME background.drawing=off
+fi
+```
+
+The condition here ensures that an extra background will be drawn around only the currently active workspace, which is great.
+
+The visual update in the bar felt a little sluggish though.
+
+## An idea for improvement
+
+I realized that I was never aware of where my windows were. I could still always cmd-tab my way around applications in standard mac fashion, but I felt I was under-utilizing my great tiling WM. I decided I wanted a different background to appear around any workspace which has an application window on it.
+
+A simple enough edit to sketchybar's aerospace plugin script.
+
+```
+# ~/.config/sketchybar/plugins/aerospace.sh
+#!/usr/bin/env bash
+
+POPULATED="$(aerospace list-windows --all --format '%{workspace}' | sort | uniq | tr '\n' ' ')"
+
+if [ "$1" = "$FOCUSED_WORKSPACE" ]; then
+ sketchybar --set $NAME background.drawing=on background.color=0xaaeab676
+else
+ if [[ $POPULATED =~ $1 ]]; then
+ sketchybar --set $NAME background.drawing=on background.color=0x8800ffff
+ else
+ sketchybar --set $NAME background.drawing=off
+ fi
+fi
+```
+
+I can first shell out to ask aerospace for the workspace that each window is on (and then unique that list), then with an extra conditional on non-active workspaces I can style them differently if they were in the list. Spruce it up with a bit of color and we look to be there!
+
+=> /~tjp/i/sketchybar-colors.jpg sketchybar with colors!
+
+But the "little sluggish" from before has become horrendous performance. It's a few seconds for this to update now.
+
+## The need for speed
+
+I had just added a shell-out to an external program into sketchybar/plugins/aerospace.sh. Looking back at how this script gets executed,
+
+- the workspace items in the sketchybar each listen to the aerospace_workspace_change event
+- they each invoke plugins/aerospace.sh with their own id as a positional argument
+
+This means that the script runs *separately for each workspace* whenever the active workspace changes. In my case this was 22 times. But that "POPULATED" list would be the same for each - I definitely don't have to collect it 22 times.
+
+So: this will be a sizable refactor, but I instead want to have a plugin script that updates the display of all 22 workspaces in the sketchybar, and then perhaps I can wire it up to only run once for every change of active workspace.
+
+First, let's update the plugin script to reset styles on *all* the workspaces.
+
+```
+# ~/.config/sketchybar/plugins/aerospace.sh
+#!/usr/bin/env bash
+
+POPULATED="$(aerospace list-windows --all --format '%{workspace}' | sort | uniq | tr '\n' ' ')"
+FOCUSED="$(aerospace list-workspaces --focused | tr '\n' ' ')"
+
+for ws in $(aerospace list-workspaces --all); do
+ if [[ $FOCUSED =~ $ws ]]; then
+ sketchybar --set space.$ws background.drawing=on background.color=0xaaeab676
+ else
+ if [[ $POPULATED =~ $ws ]]; then
+ sketchybar --set space.$ws background.drawing=on background.color=0x8800ffff
+ else
+ sketchybar --set space.$ws background.drawing=off
+ fi
+ fi
+done
+```
+
+Now let's update the sketchybar startup script as well and get rid of the "--subscribe" and "script=" on each and every workspace displayed.
+
+```
+# ~/.config/sketchybar/sketchybarrc
+sketchybar --add event aerospace_workspace_change
+
+for sid in $(aerospace list-workspaces --all); do
+ sketchybar --add item space.$sid left \
+ --set space.$sid \
+ background.color=0x44ffffff \
+ background.corner_radius=5 \
+ background.height=20 \
+ background.drawing=off \
+ label="$sid" \
+ click_script="aerospace workspace $sid"
+done
+```
+
+But, now *nothing* is subscribed to that "aerospace_workspace_change" event. This was perhaps pretty lazy, but since I just need *anything* to bridge the event to the script, and the next thing in the sketchybar is a little arrow icon...
+
+```
+sketchybar --add item chevron left \
+ --set chevron icon= label.drawing=off \
+ --subscribe chevron aerospace_workspace_change \
+ --set chevron script="$CONFIG_DIR/plugins/aerospace.sh" \
+ ...
+```
+
+Eh, it'll do. We should be ready! And it does, indeed, work. And it is, indeed, faster. Testing the script itself by just running it directly under "time", It's taking around 550ms. Passable for something that's at least only running once per interaction.
+
+## The needier for speedier
+
+I realized, though, that I hadn't gotten rid of the N+1 subprocesses problem. Namely, my script is running "sketchybar" independently for every workspace, even though you can stack up multiple --set parameters in a single call. Let's try that instead, building up the command in the loop and then just running it once.
+
+```
+# ~/.config/sketchybar/plugins/aerospace.sh
+#!/usr/bin/env bash
+
+POPULATED="$(aerospace list-windows --all --format '%{workspace}' | sort | uniq | tr '\n' ' ')"
+FOCUSED="$(aerospace list-workspaces --focused | tr '\n' ' ')"
+
+CMD="sketchybar"
+for ws in $(aerospace list-workspaces --all); do
+ if [[ $FOCUSED =~ $ws ]]; then
+ CMD+=" --set space.$ws background.drawing=on background.color=0xaaeab676"
+ else
+ if [[ $POPULATED =~ $ws ]]; then
+ CMD+=" --set space.$ws background.drawing=on background.color=0x8800ffff"
+ else
+ CMD+=" --set space.$ws background.drawing=off"
+ fi
+ fi
+done
+$CMD
+```
+
+It works, and runs in about 170ms - over 3X faster.
+
+## One bug to fix
+
+I found that if I kick a window to a different workspace with `CMD+Option+Shift+<letter>` (my keybinding for this action), of course the display in the status bar is now potentially all wrong.
+
+The nice thing about having a 170ms script that updates everything though, is you can just call it whenever you need to and not worry too much about it.
+
+In my aerospace config file I previously had:
+
+```
+cmd-alt-shift-a = 'move-node-to-workspace A'
+cmd-alt-shift-b = 'move-node-to-workspace B'
+cmd-alt-shift-c = 'move-node-to-workspace C'
+...
+```
+
+And now those directives are:
+
+```
+cmd-alt-shift-a = ['move-node-to-workspace A', 'exec-and-forget /usr/local/bin/sketchybar --trigger aerospace_workspace_change']
+cmd-alt-shift-b = ['move-node-to-workspace B', 'exec-and-forget /usr/local/bin/sketchybar --trigger aerospace_workspace_change']
+cmd-alt-shift-c = ['move-node-to-workspace C', 'exec-and-forget /usr/local/bin/sketchybar --trigger aerospace_workspace_change']
+...
+```
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/missing-the-point.md b/weblog/missing-the-point.md
new file mode 100644
index 0000000..431a9ae
--- /dev/null
+++ b/weblog/missing-the-point.md
@@ -0,0 +1,45 @@
+---
+post_date: 2023-05-29
+---
+
+# Missing The Point
+
+Daniel Stenberg (cURL author) wrote a critique of the gemini specification.
+
+[Stenberg's post](https://daniel.haxx.se/blog/2023/05/28/the-gemini-protocol-seen-by-this-http-client-person/)
+
+[thrig.me wrote a spirited defense of gemini](gemini://thrig.me/blog/2023/05/29/http-client-person-from-gemini.gmi)
+
+It is interesting to follow this conversation on the two mediums. For example, here an excerpt from the top comment on Hacker News:
+
+> I think Gemini proponents who think Daniel is missing the point \[...\] are the ones missing the point.
+
+I humbly submit that it's a bit of both. There are some clear signs that Daniel doesn't get how gemini is intended to be used, or used in practice, like this gem:
+
+> Serving an average HTML page using a number of linked resources/images over this protocol is going to be significantly slower than with HTTP/1.1 or later.
+
+Gemtext does not support CSS, JS, or inline images. Daniel does note similarities to gopher elsewhere (deridingly), but doesn't put 2 and 2 together here. Yes, gemini defines a very poor transport for the modern (large) web, _yawn_. But for gemtext documents the way the protocol is actually used (and in fact specified in the doc he read), gemini pages consistently load much, much faster than web pages in practice.
+
+Or similarly, his observation that gemini is "GET-only". I did actually laugh out loud at that one.
+
+I wish that he had successfully put himself in the headspace of "this came later than HTTP and HTML, look for the purpose behind apparent backward steps."
+
+Yet at the same time there are valuable points that Daniel is in a unique position to make, and the gemini community should take those seriously. His post ends with 6 change recommendations, and they are worthy of discussion.
+
+> 1. Split the spec into three separate ones: protocol, URL syntax, media type. Expand the protocol parts with more exact syntax descriptions and examples to supplement the English.
+> 2. Clarify the client certificate use to be origin based, not host name.
+> 3. Drop the TOFU idea, it makes for a too weak security story that does not scale and introduces massive complexities for clients.
+> 4. Clarify the UTF-8 encoding requirement for URLs. It is confusing and possibly bringing in a lot of complexity. Simplify?
+> 5. Clarify how proxying is actually supposed to work in regards to TLS and secure connections. Maybe drop the proxy idea completely to keep the simplicity.
+> 6. Consider a way to re-use connections, even if that means introducing some kind of “chunks” HTTP-style.
+
+By my count:
+- #2, #4, and #5 call out clear deficiencies in the spec,
+- #6 definitely misses the point, and
+- #1 and #3 are debatable. Changing the TOFU recommendation is probably a cultural non-starter but I'm glad he calls attention to the burden it imposes on client implementors.
+
+Kneejerk defensiveness is just going to prevent us from learning what we can, even from imperfect sources.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/neovim-lua.md b/weblog/neovim-lua.md
new file mode 100644
index 0000000..c51486d
--- /dev/null
+++ b/weblog/neovim-lua.md
@@ -0,0 +1,19 @@
+---
+post_date: 2022-06-29
+---
+
+# Neovim's lua config support
+
+I ported all my vim configs to lua over the weekend and I've never been happier with them.
+
+It's mostly a lateral move, and was rather a lot of work to have my editor function exactly the same, albeit with a loading speed improvement beneath the level of perceptibility.
+
+But the major sea change is that it now *feels changeable*. Vimscript is the ostensibly-a-programming-language that every vim user needs (and learns the bare minimum) but nobody uses on the regular. It doesn't even behave like any programming language we developers do use on a daily basis, because it's such an editor configuration DSL. Changing things in my vimscript configs was always a matter of consulting the documentation for all the things I've forgotten since the last edit, and then mostly banging on it until it appeared to work. Then of course it would frequently break when met with inputs I hadn't initially tested with.
+
+But lua is a proper programming language. I intuitively understand how to break code apart into useful functions and modules. Of course I could write vimscript functions, but they were quirky. I could break configs out into separate files, but the only way to include them was with the shell-like "source", essentially eval-ing the other file in place. Lua on the other hand exposes separate files as *modules*, and within those you can make decisions about what becomes part of the public API and what does not.
+
+Granted, so far I haven't made very full use of these capabilities, since it was already a fairly sizable task just to port vimscript to lua 1-for-1 (but I did break it into separate topical files that makes them all much more digestable as a reader). But even being a language that supports such a refactor, under language constructs that behave like I expect, makes it feel entirely different.
+
+I highly recommend making that jump, even if you don't have any particular configuration improvement to tackle simultaneously. With any sort of background in programming, it'll give you a fresh feeling that your editor is now far more configurable than before.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/nntp-a-great-protocol-for-bridging.md b/weblog/nntp-a-great-protocol-for-bridging.md
new file mode 100644
index 0000000..769d5b9
--- /dev/null
+++ b/weblog/nntp-a-great-protocol-for-bridging.md
@@ -0,0 +1,32 @@
+---
+post_date: 2023-05-17
+---
+
+# NNTP - a great protocol for bridging?
+
+This gemlog is hosted on ctrl-c.club, a "place for the curious to poke around and play." It's a multi-user unix box as a social network. We have public hosting for the web and gemini, email accounts, IRC rooms, command-line games, and a custom bulletin-board software built by a member called iris.
+
+The original iris client is a pretty bare-bones TUI, pretty much just ruby with readline and some terminal colors. As I started enjoying using it, I was also getting back into usenet newsgroups - which are still around if not exactly thriving. I realized that these systems were functionally really similar, but built on extremely different building blocks. But I wanted access to the relatively mature TUI clients available for newsgroups for browsing iris threads.
+
+So the only natural thing to do was to bridge the protocols so I could browse iris (and later post!) through NNTP clients. Iris-news was born.
+
+[iris-news: iris to NNTP bridge server](https://tildegit.org/tjp/iris-news)
+
+While it's a "server", being a bridge server it's naturally also a client at the same time. And it actually leans heavily in the "client" direction:
+- it's only intended to be accessed by the NNTP client for a single user
+- it only accepts one single connection from localhost, on an ephemeral port at that
+- it shuts down immediately when that single connection closes
+
+And it's...amazing. I love this as a mature interface to iris. A little server programming to a well-documented protocol (with a decent third-party go library), and drop in any existing client to do the ux heavy lifting. It's so good that I'm starting to want to do more.
+
+For one, mature though the newsgroup client world may be, the major TUI players I'm aware of (slrn and tin) still only connect to one server for a given session. But a bridge server could solve that for us - bridging NNTP to NNTP, but letting a client multiplex out to multiple servers. Bring in iris while we're at it.
+
+The newsgroup paradigm is also probably similar enough to the feed reader paradigm and this could be a great way to build a personal gemini reader. Just once at startup (remember this is a pretty ephemeral server) it could go fetch the latest from all the feeds I've configured it for and surface anything new. Well, surface everything and let the client figure out what's new. And since support for threading is built in, it could be more Cosmos than Antenna.
+
+Some sort of micro-blogging format could be a great fit as well, and twtxt is nice and simple.
+
+I'm increasingly thinking of the newsgroups NNTP protocol as a medium for following conversations and excited about the possibilities of a sort of bitlbee-for-newsgroups.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/obligatory-first-post.md b/weblog/obligatory-first-post.md
new file mode 100644
index 0000000..24cdcc1
--- /dev/null
+++ b/weblog/obligatory-first-post.md
@@ -0,0 +1,20 @@
+---
+post_date: 2022-05-22
+---
+
+# Obligatory "first post!"
+
+I made it. I'm a gemini blogger. A gemlogger.
+
+From browsing around it seems like there's a lot of writing about gemini, here on gemini. I get it. As one gets started writing here, gemini itself is a hot topic, very new and exciting. Then, most of us don't have the longevity to stick to a blog/gemlog for very long, and so gemini-wide, we're very heavy on "wow just found this cool gemini thing".
+
+So I'm going to resist the urge - notwithstanding that last paragraph. The strategy will be: no discussing gemini until post #6 - make it through five posts first.
+
+By way of introduction, I'm a software engineer in my late 30s. I've got a wife and kids. These days for work I do less coding and more managing teams, but I always keep a few FOSS side projects running. I'm new now to ctrl-c.club and "tildeverse" shared unix boxes in general. I hope I can find contributors on projects I start, others' projects to contribute to, and some new friends.
+
+I'm an old soul, always very interested in old technology, and intrigued with how we might get that one step back from all the times we've taken "two steps forward, one step back". I once founded a company largely about scratching that "recover the one step" itch, and I think that has a lot to do with my being here on the small web now.
+
+One down, four to go.
+-t
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/on-helicopter-parenting.md b/weblog/on-helicopter-parenting.md
new file mode 100644
index 0000000..cec92a9
--- /dev/null
+++ b/weblog/on-helicopter-parenting.md
@@ -0,0 +1,17 @@
+---
+post_date: 2022-06-14
+---
+
+# On helicopter parenting
+
+I did an interesting thing rather automatically today, and it made me think.
+
+I'm on vacation in northern Italy, and today we took a day trip over the border to Switzerland to hike around in the mountains. On the way back I was driving down the narrow swiss mountain roads when I came upon what appeared to be a mother and her two daughters, each around 8 or 10. She was looking intently at the cars on the road and holding her daughters' hands on the sidewalk. I slowed down a great deal without really thinking about it, the semi-conscious explanation being that it's better for me to have a little shorter stopping distance just in case the kids were to do something unpredictable.
+
+It wasn't until a few curves later that I realized there had been another, subconscious, reason. At this point I passed another pair of girls around the same age, this time wearing backpacks and clearly making their own way home from school, no parent in sight. They were totally at ease with their surroundings, and I went by them with a good deal more speed than I had the previous group (not really more or less than if there were an adult casually walking on the sidewalk).
+
+Why did I do that? On paper, the second group should have been more perilous - no parent around, and no heightened alertness as to the giant metal boxes flying by in close proximity. But I'm going to side with my own subconscious and say that it's the other way around. The first girls had not been sufficiently taught how to be safe for their mother to let them walk along the street alone, the seconds girls had. The first girls' mother, being on edge, put everyone around them on edge, while the second girls' cool demeaner put everyone at ease.
+
+On autopilot, I am like the first girls' mother with my own kids. I know it's my job to protect them, I can't remotely bear the thought of something happening to them, and so I do the thing that comes most naturally under those values. It takes a step back, and a smidgen of intentionality, to realize that over-protection actually endangers them. That the safest kids are the ones that know how to keep themselves safe, to a degree that the adults in their life know deep in their bones that they'll be safe.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/programming-language-learning.md b/weblog/programming-language-learning.md
new file mode 100644
index 0000000..a512dd1
--- /dev/null
+++ b/weblog/programming-language-learning.md
@@ -0,0 +1,25 @@
+---
+post_date: 2023-05-09
+---
+
+# Programming language learning
+
+I'm doing ziglings right now, currently on (checks tmux pane) number 70.
+
+[ziglings - a broken project to help you learn zig by fixing it.](https://github.com/ratfactor/ziglings)
+
+Now, to level-set: I've learned zig before. I even wrote about leveling up in zig by doing most of advent of code 2022 in it and some of the challenges I was facing at the time.
+
+[happy hacking - from January as I was doing AoC (yes, late)](./happy-hacking.md)
+
+But I haven't written any zig in around 4 months, and never did it daily for any sustained period. So going through ziglings is supposed to refresh my memory and get my skills back up. With recent advancements in the tooling and standard library (in particular package management and TLS) I'm interested in starting to use it for small web applications.
+
+What I really noticed about going through ziglings is that I'm *flying* through this. I got through 50 in a day, and spent maybe 20 minutes today getting through another 20. That's probably partly because I have some experience here already, but I know that the structure of this project (over 100 tasks) would push me towards prioritizing speed regardless. I barely read the comments (some of them are quite long), and I'm mostly kind of banging on it until the build is green. This is what concerns me.
+
+I remember actually learning zig the first time and going through ziglearn:
+
+[ziglearn - a more traditional feature rundown / tutorial](https://ziglearn.org/)
+
+and I think that while ziglings is pretty much awesome for what I'm using it for, I'd recommend actually digging in and making an honest effort to *learn material* first to anyone learning zig for the first time.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/runit-again.md b/weblog/runit-again.md
new file mode 100644
index 0000000..1bb2509
--- /dev/null
+++ b/weblog/runit-again.md
@@ -0,0 +1,33 @@
+---
+post_date: 2023-04-06
+---
+
+# Runit again
+
+I wonder how well runit shortcomings could be patched with a package of additional utilities.
+
+If there is one thing that will get nerds talking, it's Systemd. And with the percentage of geminispace being populated by us nerds, the recent resurgence of the systemd hate train / apologetics has this place buzzing.
+
+My favorite thing about that is how this is spilling over into minimal inits and daemon supervision suites, because I'm a software minimalist. Maybe not as extreme as the suckless folks, but I certainly have a deep-seeded "less is more" sensibility.
+
+[JeanG3nie had a super interesting post on 3/29](gemini://gemini.hitchhiker-linux.org/gemlog/runit_vs_s6.gmi)
+
+I didn't know much about S6 having never used it, but I was at least aware of it as being on the newer and featureful side of the daemontools-inspired process management suites. JeanG3nie points out shortcomings in runit as a result of it perhaps being overly minimal (or maybe just old). It sounds, however, like these shortcomings can all be hacked around (that post includes an example of running a one-shot service with some clever code in the run script). The problem is just that these hacks can be incompatible with one another, or suboptimal in myriad other ways. The best support for service dependencies, one-shot services, etc are when that support is built into the process manager itself.
+
+But is it? Here's where my mind started immediately racing: how many of these shortcomings really need process manager support to overcome, and how bad are the *best possible* solutions given the toolkit offered by runit?
+
+Suppose there were a package which built on top of runit capabilities to offer a single best-in-class approach for the things it's missing relative to s6 and other newer suites? The existence of a single package does a lot to overcome the compatibility problem, and when I look at ./finish, ./down, ./log, and the many other tools already offered by runit, I strongly suspect we could bridge the gap. I'd like to call this package runit_again.
+
+Why even bother when s6 and other, more capable managers already exist though? Because runit has to be the most ubiquitous of these daemontools-like suites. Distros like Void and Alpine linux[^1] run it, busybox ships with it. And again, as a software minimalist, I'd actually kind of appreciate that these more advanced features are made optional by a separate package.
+
+Still noodling on this one.
+
+~tjp
+
+*EDIT 2023-04-08*: pjvm was kind enough to email me some clarifications, so a few details have been updated.
+
+---
+
+[^1]: I was mistaken here. Alpine advertises being based on musl libc and busybox. While busybox does include a minimal runit implementation, Alpine also ships with OpenRC which it uses as pid 1 by default.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/the-baddies.md b/weblog/the-baddies.md
new file mode 100644
index 0000000..5653d7c
--- /dev/null
+++ b/weblog/the-baddies.md
@@ -0,0 +1,29 @@
+---
+post_date: 2023-04-06
+---
+
+# The Baddies
+
+The governor of Mississippi just [declared April](https://www.mississippifreepress.org/32351/gov-reeves-declares-confederate-heritage-month-a-30-year-old-mississippi-tradition) to be "Confederate Heritage Month". Turns out it's the 30th year of this tradition, but this is the first I've heard of it.
+
+I had a formative childhood experience that relates to how I took in this news. I call it formative because the shock I experienced makes it one of my earliest memories - I must have been 5 or 6, and this is one of very few memories I have from that far back.
+
+I grew up largely in the south, spending almost 9 years of my childhood in Georgia. My father comes from the south as well and so I have lots of family around there. At the time I certainly identified as a southern boy. This was one of the occasions we were visiting Stone Mountain, an enormous chunk of granite onto which are carved the images of various southern civil war generals (there are 4 including Robert E Lee and Stonewall Jackson). Like any curious child, I was asking questions about them, and thus hearing for the first time about the civil war, how the southern states fought against the northern states.
+
+Somewhere into this line of questions I asked: "but we were the good guys, right?". The shocking answer from my father that I'll never forget: "No, we were the bad guys."
+
+As a father today, I suspect I'd have been softer in my answer (I'd also have been wrong). I'd probably go into a speech a 5 or 6 year old isn't ready for, about how both sides in any war believe themselves to be in the right. I'd have muddied the waters, and I'd have let my son off the hook. But my dad met me where I was: he echoed my identity language: "we" - and leaned right into the good guys / bad guys premise.
+
+No, we were the bad guys.
+
+He left the subtle conversation debunking the good guys / bad guys dichotomy for another time - there would be time when I was older and ready for it, and he had a more important point to drive home today.
+
+I didn't want this to be the case of course. I wanted to be on the right side of history, standing up for justice, and feel the way I felt when learning about World War II. And more than wanted, I *expected* the south to be in the right - particularly in those surroundings with a giant monument to southern generals. Thus the shock.
+
+But the way my father steered the conversation led to me learning about slavery, how (despite the modern revisionism) it was the primary factor that led to the war, and how ultimately it's a good thing and a genuine American triumph that the south ("we") lost.
+
+Taking in the "Confederate Heritage Month" news today is a stark reminder of how few southern boys had a dad like mine.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/the-simple-joys-of-the-humble-slide-rule.md b/weblog/the-simple-joys-of-the-humble-slide-rule.md
new file mode 100644
index 0000000..41bfd32
--- /dev/null
+++ b/weblog/the-simple-joys-of-the-humble-slide-rule.md
@@ -0,0 +1,34 @@
+---
+post_date: 2023-05-17
+---
+
+# The simple joys of the humble slide rule
+
+A slide rule is almost the simplest possible machine.
+
+A bunch of scales printed on three rulers, the middle one of which can slide relative to the other two. Then finally, a glass box around the outside with a thin ("hairline") mark down the middle, held perpendicular to the rulers.
+
+Yet with this simple contraption you can perform all calculations normally reserved for a calculator, all the way through most Americans' high school math. I'm forever amazed by the ingenuity that enables trigonometry, powers, logarithms, and more to be calculated by rubbing two sticks together. This simple action was actively being performed in outer space by the pioneers taking part in the space race.
+
+But that's not the best part. Such a simple device is completely devoid of abstraction, of hiding the implementation away. I can use a calculator to find a square root, but I have no idea how that took place. I'm just punching in some numbers, and reading some others off. But a slide rule lays bare the fact that a square root is the value at half the distance on a logarithmic scale. Using a slide rule, you see the underlying math right in front of your face.
+
+I recently re-watched the amazing video on the fast inverse square root algorithm in the old quake 3 codebase. Like most everyone watching that today, I marvel at the genius required to invent that algorithm - "how does someone even come up with that?".
+
+[Fast Inverse Square Root - A Quake III Algorithm](https://www.youtube.com/watch?v=p8u_k2LIZyo)
+
+Except now I have at least a partial answer: by being regularly exposed to the underlying math concepts that make it possible.
+
+This all hits me right where I live.
+- It's simple and readily understood, and lays the inner workings bare.
+- It's old and retro.
+- It's deeply technical and nerdy.
+
+It's exactly the same conflation of factors that makes me love reading and writing on the small web. Simple enough protocols that everyone with some programming experience might as well build their own servers, stripping away the endless layers of obfuscation from the modern web, and taking us back to a time both technically and culturally where the web was about people. It's a similar enough sense that I renamed my small web programming toolkit to sliderule.
+
+[sliderule: the small web toolkit for go](https://code.tjp.lol/sliderule.git)
+
+And so I submit to geminispace that I suspect lots more of us here might find a fascinating diversion in learning slide rules.
+
+~tjp
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`
diff --git a/weblog/upcoming-gemini-projects.md b/weblog/upcoming-gemini-projects.md
new file mode 100644
index 0000000..6ace46a
--- /dev/null
+++ b/weblog/upcoming-gemini-projects.md
@@ -0,0 +1,46 @@
+---
+post_date: 2023-09-15
+---
+
+# Upcoming Gemini projects
+
+I've got some buns in the oven. Wait that came out wrong.
+
+[Sliderule](https://code.tjp.lol/sliderule.git) has been a great project for throwing code together covering the basics of interacting with the small web without having to make product-level choices like UX design.
+
+But it's about time I got my hands dirty with some end-user projects.
+
+## sr-71: late stage
+
+I've been working for a while already on sr-71, a full-fledged server for hosting gemini, gopher, spartan, and finger. It's now running at tjp.lol which I'm using as a test-bed for it.
+
+- [It works on gemini](gemini://tjp.lol)
+- [A little more going on in gopher, where I recently added some gophericus-like capabilities in sliderule](gopher://tjp.lol)
+- [Finger me. Wait that came out wrong](finger://tjp.lol)
+- [sr-71 repo on tildegit](https://tildegit.org/tjp/sr-71)
+
+sr-71 is still to gain at least:
+* shared hosting - support for /~username paths
+* git repo hosting (more in this in a bit)
+* I'll probably rebuild the logging in terms of the new stdlib slog pkg
+
+## showyourwork: early stage
+
+syw, or showyourwork, is a golang library for serving git repositories from disk to small web protocols via sliderule's interfaces. There isn't a public repository for it yet because it's very early days.
+
+It's going to work on all the protocols sliderule supports, and will support user-provided templates to override the generated content for git repos (but have solid defaults which are generally similar to the git support in gmcapsule [on display at skyjake's site](gemini://git.skyjake.fi/).
+
+When this is in good shape I'll add a dependency on it in sr-71 and give it git-serving abilities.
+
+## "name TBD" browser project: idea stage
+
+Bombadillo is *almost* exactly what I want. Vim-like keybindings and UX approach, multi-protocol support. I just wish it looked a little more colorful. Sort of like amfora but with finger and gopher (at least) supported.
+
+I think some of the ready-to-go capabilities of sliderule make this a pretty tractible project:
+- a multi-protocol client
+- gemtext->markdown conversion
+- gophermap->markdown conversion
+
+Add in charm's bubbletea and we've got *so much* of the hard stuff taken care of. [Bubbletea](https://github.com/charmbracelet/bubbletea) provides a TUI application framework including a nice colorful renderer of markdown content.
+
+> Note: This was ported from my old gemini capsule, still served on gemini at `gemini.ctrl-c.club/~tjp`