<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tim's blog</title><link>https://blog.thechases.com</link><description>Random ponderings</description><atom:link href="https://blog.thechases.com/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 &lt;a href="mailto:blog@tim.thechases.com"&gt;Tim Chase&lt;/a&gt; </copyright><lastBuildDate>Fri, 19 Jun 2026 14:25:20 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Vim Knowledge Management</title><link>https://blog.thechases.com/posts/vi/vim-knowledge-management/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
Another installment of 
&lt;a href="https://lazybear.social/@hyde"&gt;@hyde&lt;/a&gt;'s
&lt;a href="https://lazybea.rs/carnivals"&gt;"Vim Carnival"&lt;/a&gt;,
this time asking,
"How do you use Neovim/Vim to build your knowledge management?"
&lt;/p&gt;

&lt;h2 title="Unstructured"&gt;Unstructured Data&lt;/h2&gt;

&lt;p&gt;
While boring,
I dump all my unstructured "knowledge" data in a simple
&lt;code&gt;notes.txt&lt;/code&gt;
file within my
"personal data/text files"
repo stored in
&lt;code&gt;git&lt;/code&gt;
for syncing between machines.
&lt;/p&gt;

&lt;p&gt;
This file contains a host
of random thoughts and web-clippings.
Disambiguations between words/spellings,
URLs to interesting things with minor annotations,
notes about VPS specs/pricing,
book recommendations,
some favorite jokes,
short poetry-snippets I've composed,
whatever…all tossed together
with timestamps.
&lt;/p&gt;

&lt;p&gt;
Typically I'll either
&lt;code&gt;grep -3&lt;/code&gt;
for things in the file
(including 3 lines of context)
or I'll open it in any
&lt;code&gt;$EDITOR&lt;/code&gt;
to search for things.
&lt;/p&gt;

&lt;p&gt;
I also have a keyboard shortcut
in my window-manager
(&lt;a href="https://fluxbox.org/"&gt;Fluxbox&lt;/a&gt;)
to launch a
&lt;a href="https://blog.thechases.com/posts/vi/vim-knowledge-management/#save"&gt;shell-script&lt;/a&gt;
that uses
&lt;code&gt;zenity&lt;/code&gt;
to prompt me for a description,
then appends the current date+time,
the one-line description,
and the contents of the clipboard
(using &lt;code&gt;xsel&lt;/code&gt;)
to this file.
&lt;/p&gt;

&lt;h2 title="structured"&gt;Structured Data&lt;/h2&gt;

&lt;p&gt;
Depending on the nature of the structured
usually specific tooling gets used to manipulate it.
&lt;/p&gt;

&lt;h3 id="addressbook"&gt;Address book&lt;/h3&gt;

&lt;p&gt;
I keep my personal address book in
&lt;a href="https://www.gnu.org/software/recutils/"&gt;GNU RecUtils&lt;/a&gt;
format.
This plain-text database format
easily lets me add new fields as I have need.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
# people
id: davesmith
household: smith
last: Smith
first: David
nickname: Dave
mother: alicesmith
father: bobsmith
dob: Apr 1 2015
note: in 5th grade class with S

id: alicesmith
household: smith
last: Smith
first: Alice
spouse: bobsmith
phone: 972.555.1212 (c)
phone: 817.555.1234 (h)
email: alice@example.net

⋮

# households
hid: smith
anniversary: Jun 10 2006
street: 3141 Oak St.
city: Anytown
state: PA
zip: 31415
&lt;/pre&gt;
&lt;figcaption&gt;a sample &lt;code&gt;addressbook.txt&lt;/code&gt; excerpt&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Most of the time,
I find it easiest to just open the file in my
&lt;code&gt;$EDITOR&lt;/code&gt;
and search for things there
when I need to look someone up.
But I have a few canned queries for doing things like
generating the email contact list for our Christmas email letter.
&lt;/p&gt;

&lt;h3 id="calendar"&gt;Calendar&lt;/h3&gt;

&lt;p&gt;
I use
&lt;a href="https://dianne.skoll.ca/projects/remind/"&gt;Remind&lt;/a&gt;
for calendar-related data.
Thanks to
&lt;a href="https://www.gnu.org/software/recutils/manual/recutils.html"&gt;&lt;code&gt;recsel&lt;/code&gt;&lt;/a&gt;
and a little
&lt;code&gt;awk&lt;/code&gt;
scripting,
I can generate
&lt;code&gt;remind&lt;/code&gt;
entries for
birthdays,
anniversaries,
adoptions,
memorials,
etc.
Then
&lt;code&gt;remind&lt;/code&gt;
can
&lt;code&gt;INCLUDE&lt;/code&gt;
the resulting files.
&lt;/p&gt;

&lt;p&gt;
For other appointments,
they go directly into the corresponding reminder files
—
one for me,
one for my wife, 
one for each kid individually, 
one for the kids combined,
one for the whole household,
one for work,
one for church,
…
&lt;/p&gt;

&lt;p&gt;
I have a
&lt;code&gt;cron&lt;/code&gt;
job that sends each day's
&lt;code&gt;remind&lt;/code&gt;
output to my email,
so I have a copy of the day's reminders and
&lt;a href="https://blog.thechases.com/posts/vi/vim-knowledge-management/#todos"&gt;todo items&lt;/a&gt;.
&lt;/p&gt;

&lt;h3 id="finances"&gt;Finances&lt;/h3&gt;

&lt;p&gt;
I track these as
&lt;a href="https://plaintextaccounting.org/"&gt;plain-text accounting&lt;/a&gt;
plain-text 
which allows me to manage my finances using
&lt;a href="https://www.ledger-cli.org/"&gt;&lt;code&gt;ledger&lt;/code&gt;&lt;/a&gt;.
&lt;/p&gt;

&lt;h3 id="todos"&gt;&lt;strike&gt;Todos&lt;/strike&gt;&lt;/h3&gt;

&lt;p&gt;
For a long time I kept todo items in
&lt;a href="http://todotxt.org/"&gt;&lt;code&gt;todo.txt&lt;/code&gt;&lt;/a&gt;
format,
and had a symlink for my
&lt;code&gt;~/.plan&lt;/code&gt;
so I could use
&lt;code&gt;finger(1)&lt;/code&gt;
from other machines to fetch my current todo list.
&lt;/p&gt;

&lt;p&gt;
That said, since
&lt;code&gt;remind&lt;/code&gt;
has grown todo-tracking functionality,
most of my todo items have moved into a 
&lt;code&gt;todo.rem&lt;/code&gt;
reminder file.
And I can express repeating todo-items a
&lt;strong&gt;lot&lt;/strong&gt;
more easily now.
&lt;/p&gt;

&lt;h2 title="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;
But the best part of all of this?
&lt;strong&gt;It's all plain text.&lt;/strong&gt;
I store it in
&lt;code&gt;git&lt;/code&gt;
and can edit it with any editor,
whether
&lt;code&gt;vim&lt;/code&gt;,
&lt;code&gt;vi&lt;/code&gt;,
&lt;code&gt;ed(1)&lt;/code&gt;,
or even (as noted above)
generated by shell commands.
&lt;/p&gt;

&lt;hr&gt;

&lt;p id="save"&gt;
If interested, here's the shell-script

&lt;/p&gt;&lt;pre&gt;
#!/bin/sh
SAVE_FILE="$HOME/notes.txt"
DESC="$(zenity --title 'Enter description' --entry --text 'Enter description of clipboard contents')"
if [ -n "${DESC}" ]
then
 echo &lt;gt&gt;&lt;gt&gt; $SAVE_FILE
 date +"%Y-%m-%d %H:%M" &lt;gt&gt;&lt;gt&gt; $SAVE_FILE
 echo "${DESC}" &lt;gt&gt;&lt;gt&gt; $SAVE_FILE
 xclip -o -selection clipboard &lt;gt&gt;&lt;gt&gt; $SAVE_FILE
 echo &lt;gt&gt;&lt;gt&gt; $SAVE_FILE
fi
&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/gt&gt;&lt;/pre&gt;</description><category>vi</category><category>vim</category><guid>https://blog.thechases.com/posts/vi/vim-knowledge-management/</guid><pubDate>Mon, 15 Jun 2026 19:06:39 GMT</pubDate></item><item><title>Vim Carnival: Motion</title><link>https://blog.thechases.com/posts/vi/vim-carnival-motion-changed/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
&lt;a href="https://lazybear.social/@hyde"&gt;@hyde&lt;/a&gt;
over on Mastodon
issued a call for
&lt;a href="https://lazybea.rs/carnivals"&gt;"Vim Carnival"&lt;/a&gt;
posts, with this month's topic suggesting
&lt;a href="https://lazybea.rs/vim-carnival-202606/"&gt;the
&lt;code&gt;vim&lt;/code&gt; motion that changed everything&lt;/a&gt;.
&lt;/p&gt;

&lt;p&gt;
Buckle up, because you get not
&lt;strong&gt;one&lt;/strong&gt;
but
&lt;strong&gt;multiple&lt;/strong&gt;
motions that changed things.
&lt;/p&gt;

&lt;h2 id="boring"&gt;The Boring Motion(s)&lt;h2&gt;

&lt;/h2&gt;&lt;/h2&gt;&lt;p&gt;
While boring,
you get 6 motions for the price of one:
&lt;code&gt;f&lt;/code&gt;/&lt;code&gt;F&lt;/code&gt;/&lt;code&gt;t&lt;/code&gt;/&lt;code&gt;T&lt;/code&gt;
jump forward/backward
landing on
(or immediately short of)
a target character;
and
&lt;code&gt;,&lt;/code&gt;/&lt;code&gt;;&lt;/code&gt;.
to repeat that motion
in either the same or opposite direction.
I use these
&lt;em&gt;all. the. time.&lt;/em&gt;
And when I find myself using other
&lt;code&gt;$EDITOR&lt;/code&gt;s
that lack this functionality,
it feels so slow moving around horizontally.
&lt;/p&gt;

&lt;h2 id="epiphany"&gt;The Epiphany Motion&lt;h2&gt;

&lt;/h2&gt;&lt;/h2&gt;&lt;p&gt;
While simple, the
&lt;code&gt;H&lt;/code&gt;/&lt;code&gt;L&lt;/code&gt;
motions hold a special place in my
&lt;code&gt;vim&lt;/code&gt;-learning path.
Not so much the raw motions themselves
(which I had used quite regularly),
but because they represent a step in
&lt;code&gt;vim&lt;/code&gt;
intuition.
At one point I wanted to jump a couple lines
&lt;em&gt;below&lt;/em&gt;
the top line on the screen.
Without knowing whether they accepted a
&lt;code&gt;{count}&lt;/code&gt;
I instinctively issued something like
&lt;code&gt;4H&lt;/code&gt;
and to my delight,
it worked exactly as I had hoped/expected.
So rather than search
or use relative line-numbers
(which tend to get a bit laggy
over a slow or high-latency
&lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt;
connection)
I often find it easier to make a coarse jump
to an estimated line near the top/bottom of the screen
and adjust up/down a line or two if needed.
&lt;/p&gt;</description><category>vi</category><category>vim</category><guid>https://blog.thechases.com/posts/vi/vim-carnival-motion-changed/</guid><pubDate>Mon, 15 Jun 2026 18:25:24 GMT</pubDate></item><item><title>Unlocking secondary disks on OpenBSD</title><link>https://blog.thechases.com/posts/bsd/unlocking-secondary-disks/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
In a recent conversation,
the topic of encrypted disks on
&lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;
came up,
and how to have multiple
&lt;abbr title="Full Disk Encryption"&gt;FDE&lt;/abbr&gt;
disks while only needing to enter a passphrase once.
If you install to a FDE root,
and then follow
&lt;a href="https://www.openbsd.org/faq/faq14.html#softraidFDE"&gt;the
official instructions for setting up FDE&lt;/a&gt;
on your additional drive(s),
you'll likely end up needing to enter your passphrase
multiple times during boot,
once for each drive
or provide a key-disk.
Thus began the quest to document how
to unlock the root disk with a single passphrase
and then have the other disk(s) unlock automatically.
&lt;/p&gt;

&lt;p&gt;
This assumes you've already configured the first
boot-drive with FDE using the installer.
&lt;/p&gt;

&lt;h2 id="quiesced-system"&gt;Get a quiesced system&lt;/h2&gt;

&lt;p&gt;
To begin with, make sure that all drives are attached
and you have rebooted into the system to ensure drive-naming quiesces.
I found that using a USB drive as my
&lt;code&gt;$ENCRYPTED_DISK&lt;/code&gt;
led to it being "sd2" after initially plugging it in,
but "sd0" upon rebooting.
You want to ensure stable naming.
&lt;/p&gt;

&lt;h2 id="initial-setup"&gt;Initial setup&lt;/h2&gt;

To make the following instructions easy to adapt,
we'll set some initial variables that you can change
to suit your needs:

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;KEYFILE=&lt;var&gt;/root/keyfile&lt;/var&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;ENCRYPTED_DISK=&lt;var&gt;sd2&lt;/var&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;DEST=&lt;var&gt;/mnt/data&lt;/var&gt;
&lt;/pre&gt;
&lt;figcaption&gt;Helpful environment variables&lt;/figcaption&gt;
&lt;/figure&gt;

The
&lt;code&gt;$KEYFILE&lt;/code&gt;
points to the password/passphrase file
owned by the "root" user,
and with 0600 permissions
("&lt;code&gt;-rwx------&lt;/code&gt;",
which can be done by temporarily setting a
&lt;code&gt;umask&lt;/code&gt;
of "077")
that we need to generate with high-quality random gibberish:

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;OLD_UMASK=$(umask)
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;umask 077
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;tr -dc a-zA-Z0-9 &amp;lt; /dev/urandom |
fold -256 |
head -1 &amp;gt; $KEYFILE
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;umask $OLD_UMASK
&lt;/pre&gt;
&lt;figcaption&gt;Creating the keyfile&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="disk-setup"&gt;Disk setup&lt;/h2&gt;

&lt;p&gt;
WARNING: this will destroy the data on
&lt;code&gt;$ENCRYPTED_DISK&lt;/code&gt;!
&lt;/p&gt;

&lt;p&gt;
Use
&lt;code&gt;fdisk&lt;/code&gt;
and
&lt;code&gt;disklabel&lt;/code&gt;
to create a full-disk-spanning
"a" partition of type "RAID":
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;dd if=/dev/urandom of=/dev/r${ENCRYPTED_DISK}c bs=1m
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;fdisk -iy $ENCRYPTED_DISK
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;disklabel -E $ENCRYPTED_DISK
&lt;label&gt;&lt;var&gt;sd2&lt;/var&gt;&amp;gt; &lt;/label&gt; a
&lt;label&gt;partition to add: [a]&lt;/label&gt;
&lt;label&gt;offset: [64]&lt;/label&gt;
&lt;label&gt;size: […]&lt;/label&gt;
&lt;label&gt;FS type: [4.2BSD]&lt;/label&gt; &lt;mark&gt;RAID&lt;/mark&gt; 
&lt;label&gt;&lt;var&gt;sd2&lt;/var&gt;*&amp;gt; &lt;/label&gt; q
&lt;label&gt;Write new label?: [y]&lt;/label&gt;
&lt;/pre&gt;
&lt;figcaption&gt;Setting up the target disk&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Next, create the encrypted drive
("sd3" here as reported from
&lt;code&gt;bioctl&lt;/code&gt;
output):

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;bioctl -c C -p ${KEYFILE} -l ${ENCRYPTED_DISK}a softraid0
softraid0: CRYPTO volume attached as &lt;mark&gt;sd3&lt;/mark&gt;
&lt;/pre&gt;
&lt;figcaption&gt;Setting up the encrypted device&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/p&gt;&lt;p&gt;
Now let's prepare that resulting disk for usage.
You can use
&lt;code&gt;disklabel&lt;/code&gt;
to partition it however you want,
but for the example here,
we'll just create a single big "a" partition.
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;DECRYPTED_DISK=&lt;var&gt;sd3&lt;/var&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;dd if=/dev/zero of=/dev/r${DECRYPTED_DISK}c bs=1m count=1
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;fdisk -iy $DECRYPTED_DISK
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;disklabel -E $DECRYPTED_DISK
&lt;label&gt;&lt;var&gt;sd3&lt;/var&gt;&amp;gt; &lt;/label&gt;a
&lt;label&gt;partition to add: [a]&lt;/label&gt;
&lt;label&gt;offset: [64]&lt;/label&gt;
&lt;label&gt;size: […]&lt;/label&gt;
&lt;label&gt;FS type: [4.2BSD]&lt;/label&gt;
&lt;label&gt;&lt;var&gt;sd3&lt;/var&gt;*&amp;gt; &lt;/label&gt; q
&lt;label&gt;Write new label?: [y]&lt;/label&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;newfs ${DECRYPTED_DISK}a
&lt;/pre&gt;
&lt;figcaption&gt;Formatting the encrypted disk&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Repeat the
&lt;code&gt;newfs&lt;/code&gt;
for other partitions if you created any.
&lt;/p&gt;

&lt;h2 title="reproducing"&gt;Reproducing it on reboot&lt;/h2&gt;

&lt;p&gt;
To re-engage things upon rebooting,
we need to obtain the disk-id:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;DUID="$(disklabel $DECRYPTED_DISK | awk '$1 == "duid:"{print $2}')"
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;echo $DUID
c001ba1dc0dedad5
&lt;/pre&gt;
&lt;figcaption&gt;Determining the Disk ID&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
With that,
we can create entries in
&lt;code&gt;/etc/fstab&lt;/code&gt;:

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;echo "${DUID}.a $DEST ffs rw,noauto&lt;var&gt;,nodev,nosuid&lt;/var&gt; 0 0" &amp;gt;&amp;gt; /etc/fstab
&lt;/pre&gt;
&lt;figcaption&gt;Creating a &lt;code&gt;/etc/fstab&lt;/code&gt; entry&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/p&gt;&lt;p&gt;
You can remove the "nodev" and "nosuid" if you need those.
Add additional entries if you created and formatted other partitions.
At this point, you should be able to mount the decrypted drive:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;mount $DEST
&lt;/pre&gt;
&lt;figcaption&gt;Mounting the encrypted partition&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
Finally, add commands in
&lt;code&gt;/etc/rc.local&lt;/code&gt;
to create the crypto device, and mount it at boot:
&lt;/p&gt;

&lt;figure&gt;
&lt;pre&gt;
&lt;label class="root"&gt;root@openbsd# &lt;/label&gt;cat &amp;gt;&amp;gt; /etc/rc.local &amp;lt;&amp;lt;EOF
bioctl -c C -p ${KEYFILE} -l ${ENCRYPTED_DISK}a softraid0
mount "$DEST"
EOF
&lt;/pre&gt;
&lt;figcaption&gt;Adding commands to &lt;code&gt;/etc/rc.local&lt;/code&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
If you have other partitions and fstab(5) entries,
you can add additional mount(8) directives.
&lt;/p&gt;

&lt;p&gt;
Finally you should be able to reboot into the new system,
enter your main/root drive's password/passphrase,
and yet have your second drive also unlocked
during the boot process
without needing to provide a second password/passphrase.
&lt;/p&gt;

&lt;p&gt;
You can back up the
&lt;code&gt;$KEYFILE&lt;/code&gt;
file by copying it to a USB drive,
sending it to a remote machine,
or using whatever backup methods you're currently using.
Just ensure that you keep it secure.
Otherwise, if your main/root drive goes down,
you'll lose access to the second drive, too.
&lt;/p&gt;</description><category>openbsd</category><guid>https://blog.thechases.com/posts/bsd/unlocking-secondary-disks/</guid><pubDate>Sun, 08 Mar 2026 18:14:54 GMT</pubDate></item><item><title>Why BSDs?</title><link>https://blog.thechases.com/posts/why-bsds/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
Every week or two,
&lt;a href="https://blog.thechases.com/posts/why-bsds/#sample-posts"&gt;a post seems to pop up&lt;/a&gt;
on one of the BSD-related sub-reddits
where I hang out
(&lt;a href="https://reddit.com/r/FreeBSD"&gt;FreeBSD&lt;/a&gt;,
&lt;a href="https://reddit.com/r/OpenBSD"&gt;OpenBSD&lt;/a&gt;,
&lt;a href="https://reddit.com/r/NetBSD"&gt;NetBSD&lt;/a&gt;,
&amp;amp;
&lt;a href="https://reddit.com/r/BSD"&gt;BSD&lt;/a&gt;)
asking why folks would choose
a BSD over a Linux distribution.
Having answered the same question multiple times,
I figured the time had come to post my "why" here.
&lt;/p&gt;

&lt;a href="https://blog.thechases.com/posts/why-bsds/#push"&gt;Skip the history
and jump to the push/pull&lt;/a&gt;

&lt;h2 id="early-unix"&gt;Early Unix experiences
(1987–1994)&lt;/h2&gt;

&lt;p&gt;
I'd grown up on Apple DOS 3.3 &amp;amp; ProDOS,
then moved to MS-DOS 4.x and following.
But then I had taste of Unix
via our local college's
dial-in shell servers
and Unix labs.
Using a Unix command-line felt similar,
but as if crafted with design and intent.
Pieces played together nicely.
Programs multitasked in ways that made sense
(and I could never do on DOS).
Countless late nights via 1200 baud dial-up.
I became hooked.
&lt;/p&gt;

&lt;h2 id="initial-install"&gt;Initial installation &amp;amp; exploration
(1995–1996 school year)&lt;/h2&gt;

&lt;p&gt;
After downloading umpty-gazillion 3½" floppies
and schlepping them back to my dorm,
an older CompSci classmate helped me install
Slackware on my computer.
That powerhouse 486DX/100
with 32MB of RAM
and two 512MB hard-drives
(a whole
&lt;em&gt;GIGABYTE&lt;/em&gt;,
baby!).
To this day,
I still have a dislike of Emacs
that might stem from the E-series
Slackware floppies getting corrupted,
driving me back to the lab
to download and write fresh disks
just so Emacs would install.
&lt;/p&gt;

&lt;p&gt;
Meanwhile, my favorite campus computer lab held networked
&lt;a href="https://en.wikipedia.org/wiki/Ultrix"&gt;DEC Ultrix&lt;/a&gt;
workstations running X
at mind-blowing resolutions and color-depth
compared to the 800x600 SVGA graphics
on my personal computer.
A real Unix.
&lt;/p&gt;

&lt;p&gt;
While I enjoyed having Slackware,
the lack of internet access in the dorm room
made it a challenge to make productive use of it.
DOS (with Windows 3.1 on demand) at least
let me write papers in WordPerfect 5.1
and take the files to labs to print them out.
But then I discovered HTML.
It felt like WordPerfect's
&lt;q&gt;Reveal Codes&lt;/q&gt;
functionality,
letting me clearly see what my document contained under the hood.
No hidden stray markup,
causing images to misalign
or change the spacing
or bold/italicize/underline the wrong thing.
I could also write it in a normal text editor,
whether on DOS,
in Windows,
or in my Slackware install
(modulo line-ending concerns),
letting it become more useful.
In addition to those benefits,
while the computer labs charged
$0.07 per page to print,
I discovered that the college library
let me print "web resources"
for free.
Guess who could point the web-browser
at his paper on the floppy drive,
open it as a "web resource"
and print it for free?
&lt;/p&gt;

&lt;h2 id="experimentation"&gt;College experimentation&lt;/h2&gt;

&lt;p&gt;
As college progressed,
I tried installing and reinstalling other flavors of Linux.
Red Hat 8 ("Psyche") &amp;amp; Mandrake
had a great install experience,
but
&lt;abbr title="RedHat Package Manager"&gt;RPM&lt;/abbr&gt;
had issues that required me to manually install
package dependencies.
Download the
&lt;tt&gt;.rpm&lt;/tt&gt;
file, try to install it,
discover missing dependencies,
download those
&lt;tt&gt;.rpm&lt;/tt&gt;
files, install, rinse, repeat until things finally installed.
&lt;/p&gt;

&lt;p&gt;
I also tried installing FreeBSD
(Walnut Creek CD-ROMs, around version 2.x)
in this time-frame.
However I failed to figure out the installer.
With the benefit of hindsight,
I know the primary issue
stemmed from "partition" meaning one thing
in DOS &amp;amp; Linux,
while meaning a different thing in FreeBSD
(where "slices" referred to what I knew as
&lt;abbr title="Master Boot Record"&gt;MBR&lt;/abbr&gt;
"partitions" in DOS,
while FreeBSD referred to subdivisions
of one of those "slices" as "partitions").
Had I understood the differing terminology at the time,
I might have successfully installed FreeBSD.
&lt;/p&gt;

&lt;h2 id="post-college"&gt;Post college Linux&lt;/h2&gt;

&lt;p&gt;
Now out of college and married,
we ordered a new
&lt;a href="https://dmesgd.nycbug.org/index.cgi?do=view&amp;amp;id=4465"&gt;Gateway Solo 1200 laptop&lt;/a&gt;.
An 800MHz Celeron processor,
128MB of RAM,
and 10GB of disk-space!
It came with Windows ME,
a horrible OS, but with
&lt;a href="https://en.wikipedia.org/wiki/Winmodem"&gt;WinModem&lt;/a&gt;
drivers that worked where
&lt;a href="https://www.knopper.net/knoppix/index-en.html"&gt;Knoppix&lt;/a&gt;
and other attempts at Linux
refused to get online.
Once we had a
&lt;abbr title="Digital Subscriber Line, offering always-on internet at higher speeds than dialup"&gt;DSL&lt;/abbr&gt;
connection in the apartment,
I maxed out the RAM to 320MB,
swapped in a 120GB hard-drive,
and installed Debian
which served us well for many years.
&lt;/p&gt;

&lt;p&gt;
Curious about Apple hardware &amp;amp; operating systems,
we picked up an
&lt;a href="https://dmesgd.nycbug.org/index.cgi?do=view&amp;amp;id=4528"&gt;Apple iBook G4&lt;/a&gt;,
the last of their
&lt;abbr title="PowerPC"&gt;PPC&lt;/abbr&gt;
line,
running at 1.3GHz,
maxed out with 1.5GB of RAM,
and a 30GB hard-drive.
&lt;/p&gt;

&lt;p&gt;
The Mac drove me bonkers.
&lt;/p&gt;

&lt;p&gt;
I imagine that folks exist who think the way Apple does.
And I imagine that OSX works fabulously for them.
I
&lt;em&gt;am not&lt;/em&gt;
such a person.
Without support for new OS versions
(PPC support stopped with 10.4 or 10.5),
the laptop rapidly became a useless brick,
unsafe to use on the open internet.
&lt;/p&gt;

&lt;p&gt;
I found a good deal
on some Lenovo laptops at (the now-defunct) Fry's.
I installed Debian on mine,
and Ubuntu on my sweetheart's.
They served us fairly well for years
of web-browsing,
email,
light gaming,
remoting into machines at work,
and software development for me.
&lt;/p&gt;

&lt;h2 id="push"&gt;The Push: the slow decline of Linux&lt;/h2&gt;

&lt;p&gt;
However, grey clouds started rolling in.
Debian started introducing changes that drifted
farther and farther from the Unix I started with,
and I did
&lt;em&gt;not&lt;/em&gt;
care for it.
&lt;/p&gt;

&lt;p&gt;
&lt;span id="audio"&gt;Sound systems&lt;/span&gt;
churned leading to frustration.
I lived through
OSS,
libao,
ESD,
aRTS,
ALSA,
Pulse,
Jack,
and now Pipewire.
Each one promised to
&lt;a href="https://xkcd.com/927/"&gt;fix all the problems
of the previous standards&lt;/a&gt;.
Similarly, I lived through a plurality of firewall tech.
&lt;/p&gt;

&lt;p&gt;
Then programs I'd used for years/decades
began to issue deprecation warnings.
"Don't use
&lt;code id="ifconfig"&gt;ifconfig&lt;/code&gt;
to manage your wireless connection use
&lt;code&gt;iwconfig&lt;/code&gt;,
and to manage your bridge devices use
&lt;code&gt;brctl&lt;/code&gt;,
except don't use either of those, use
&lt;code id="ip"&gt;ip&lt;/code&gt;
instead.
Fresh installs of most popular distributions lacked
&lt;code&gt;ed(1)&lt;/code&gt;
in the base system,
so when things went sideways,
you might not have a
&lt;code&gt;$EDITOR&lt;/code&gt;
to rescue your system.
&lt;/p&gt;

&lt;p&gt;
I would open a
&lt;code id="man"&gt;man&lt;/code&gt;
page to read documentation,
only to find a useless placeholder stub
redirecting me to a GNU
&lt;code id="info"&gt;info&lt;/code&gt;
page where you had to navigate to different parts to see them,
and you couldn't read the whole thing in one place.
I've since learned to use
&lt;code&gt;info &lt;var&gt;program&lt;/var&gt; | less&lt;/code&gt;
to force
&lt;code&gt;info&lt;/code&gt;
to render the entire document into
&lt;code&gt;less&lt;/code&gt;
where I can comfortably read the whole thing.
&lt;/p&gt;

&lt;p&gt;
I'd reach for
&lt;code id="netstat"&gt;netstat&lt;/code&gt;
only to hear that I should use
&lt;code&gt;ss&lt;/code&gt;
instead.
Or that I should stop using
&lt;code id="nslookup"&gt;nslookup&lt;/code&gt;
that I'd used for years on *nix and Windows
and instead use
&lt;code&gt;host&lt;/code&gt;
or
&lt;code&gt;dig&lt;/code&gt;/&lt;code&gt;drill&lt;/code&gt;.
&lt;ins class="note"&gt;EDIT: A
&lt;a href="https://io.mwl.io/@mwl/115551355915805641"&gt;note
from author Michael W. Lucas&lt;/a&gt;
informs me that the
&lt;strong&gt;developers&lt;/strong&gt;
of
&lt;code id="nslookup"&gt;nslookup&lt;/code&gt;
deprecated their own application,
not Linux distro-builders mandating change.
&lt;/ins&gt;
&lt;/p&gt;

&lt;p&gt;
Then came the big ones:
&lt;code&gt;systemd&lt;/code&gt;
and the threat of Wayland replacing Xorg.
&lt;code&gt;systemd&lt;/code&gt;
&lt;a href="https://github.com/tmux/tmux/issues/428"&gt;broke detaching in &lt;code&gt;tmux&lt;/code&gt;&lt;/a&gt;
and rudely expected the
&lt;code&gt;tmux&lt;/code&gt;
team to fix the problems that
&lt;code&gt;systemd&lt;/code&gt;
had caused.
I would issue shutdown/reboot commands
with the power of root
(&lt;code&gt;sudo shutdown -r now&lt;/code&gt;)
only to have 
&lt;code&gt;systemd&lt;/code&gt;
balk and refuse to actually shutdown/reboot,
hanging indefinitely on some process
over which I had no control.
Thank goodness for the ability to hold down the power-button
and kill it properly.
No choice whether I wanted
&lt;code&gt;systemd&lt;/code&gt;
vs some other init system.
I could no longer
&lt;code&gt;grep&lt;/code&gt;
my log files because 
&lt;code&gt;systemd&lt;/code&gt;
kept them in binary formats.
&lt;/p&gt;

&lt;p&gt;
Meanwhile, I've heard strong rumblings
that Xorg will get supplanted by Wayland
in most Linux distributions.
My window-manager of preference (fluxbox)
does not work in Wayland.
Many of the GUI applications that I use
do not work in Wayland.
I continue to hear that
&lt;em&gt;a lot&lt;/em&gt;
of things don't currently work in Wayland.
Maybe it will meet my needs someday,
but not any time in the imminent future.
&lt;/p&gt;

&lt;h2 id="pull"&gt;The Pull: the BSDs (2012–)&lt;/h2&gt;

&lt;p&gt;
Meanwhile, amid all those pushes in the Linux world
I found myself drawn to features
that FreeBSD &amp;amp; OpenBSD offered.
&lt;/p&gt;

&lt;p&gt;
While FreeBSD does offer several firewalls,
those choices include
&lt;code&gt;pf&lt;/code&gt;
which quickly became my favorite firewall syntax.
Originating in OpenBSD,
it gives me
&lt;em&gt;one&lt;/em&gt;
sensible syntax
to manage my firewalls
on all my machines.
&lt;/p&gt;

&lt;p&gt;
ZFS frees me from getting locked into a particular partition layout,
pooling my available storage and making it available to all datasets.
It checksums my data before writing and after reading
to ensure that my data hasn't bitrotted.
It gives me transparent compression.
I can do instant snapshots of my data.
I can clone datasets and send/receive that data efficiently across to other machines for backup.
Copy-on-write meant that
even if my system experienced abrupt power-loss
the file-system remained consistent
and didn't require a
&lt;code&gt;fsck&lt;/code&gt;
upon reboot.
&lt;/p&gt;

&lt;p&gt;
FreeBSD gives me jails
which make
&lt;em&gt;a lot&lt;/em&gt;
more sense to me than containerization in Linux-land.
As an added benefit,
FreeBSD has offered jails far longer (March of 2000)
than Linux containers,
giving them more time to bake.
&lt;/p&gt;

&lt;p&gt;
Meanwhile, OpenBSD gives me a system that feels good.
Applications and services play well together like
&lt;code&gt;dhcpd&lt;/code&gt;
talking to
&lt;code&gt;pf&lt;/code&gt;,
&lt;code&gt;unbound&lt;/code&gt;,
or
&lt;code&gt;relayd&lt;/code&gt;.
And it includes
&lt;code&gt;xenodm&lt;/code&gt;/&lt;code&gt;xenocara&lt;/code&gt;
with a base install,
and includes three window managers:
&lt;code&gt;fvwm&lt;/code&gt; (the default),
&lt;code&gt;twm&lt;/code&gt; (old school),
and
&lt;code&gt;cwm&lt;/code&gt; (my favorite of the three).
&lt;/p&gt;

&lt;h2 id="switch"&gt;Finally switching from Debian to FreeBSD/OpenBSD (2019)&lt;/h2&gt;

&lt;p&gt;
The final nail in the coffin
came from a Debian upgrade where
&lt;code&gt;systemd&lt;/code&gt;
took down my audio subsystem completely
and started having problems booting reliably.
&lt;/p&gt;

&lt;p&gt;
I backed up my data to an external drive
along with a list of the major software I use,
sent a copy to my
&lt;abbr title="Virtual Private Server"&gt;VPS&lt;/abbr&gt;,
and installed FreeBSD on my daily driver.
I copied all my data back,
installed the major software on FreeBSD,
and went on with life largely as I had before.
&lt;/p&gt;

&lt;p&gt;
I also set up some other junker laptops with OpenBSD,
including that now-dead Gateway Solo 1200,
and that iBook G4,
as well as my
&lt;a href="https://www.reddit.com/r/writerDeck/"&gt;writer-deck&lt;/a&gt;,
a Dell Mini10 netbook.
Additionally, my VPS instances run a mix of
FreeBSD &amp;amp; OpenBSD.
&lt;/p&gt;

&lt;h2 id="epilogue"&gt;Epilogue&lt;/h2&gt;

&lt;p&gt;
Is everything perfect?
I still experience minor issues,
most notably the audio on FreeBSD
doesn't automatically cut over
between speakers and headphones
when I plug/unplug headphones.
&lt;/p&gt;

&lt;p&gt;
But it feels like the Unix I grew up using.
&lt;/p&gt;

&lt;p&gt;
&lt;strong&gt;It feels like home.&lt;/strong&gt;
&lt;/p&gt;


&lt;hr id="sample-posts"&gt;

A few such sample posts:
&lt;a href="https://www.reddit.com/r/freebsd/comments/10ute8p/when_to_daily_drive_freebsd_over_linux/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1fjtg9v/why_do_some_people_prefer_unix_to_linux/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1gsy1c8/deleted_by_user/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1m5lvgk/freebsd_as_daily_driver/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1mwr8fb/advantages_of_freebsd_over_linux/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1n30kxi/why_free_bsd/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1nbfhhu/give_me_some_reasons_for_someone_to_use_free_bsd/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1ncvea9/former_linux_users_whyd_you_swich/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1o4gzhf/tell_us_about_your_story_why_you_went_freebsd/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/freebsd/comments/1oeeed6/benefits_of_freebsd/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/openbsd/comments/1lot5x8/should_i_run_openbsd_or_something_else/"&gt;here&lt;/a&gt;,
&lt;a href="https://www.reddit.com/r/openbsd/comments/1n9ginr/what_are_the_advantages_of_openbsd_over_any_linux/"&gt;here&lt;/a&gt;,
and
&lt;a href="https://www.reddit.com/r/BSD/comments/1l3wjsn/why_do_people_use_openbsd_as_their_desktop/"&gt;here&lt;/a&gt;.</description><guid>https://blog.thechases.com/posts/why-bsds/</guid><pubDate>Fri, 14 Nov 2025 18:10:03 GMT</pubDate></item><item><title>Assorted less(1) tips</title><link>https://blog.thechases.com/posts/assorted-less-tips/</link><dc:creator>Tim Chase</dc:creator><description>&lt;aside&gt;
"word to your moms,
I came to drop bombs,
I've got more
&lt;code&gt;less&lt;/code&gt;
tips
than the Bible's got Psalms."
&lt;/aside&gt;

&lt;p&gt;
In
&lt;a href="https://www.reddit.com/r/commandline/comments/1oteeaj/whats_the_most_useful_commandline_trick_you/no5xkx4/"&gt;a
recent discussion on Reddit&lt;/a&gt;
I shared a number of tips
about the common utility
&lt;a href="https://man.openbsd.org/less.1"&gt;&lt;code&gt;less(1)&lt;/code&gt;&lt;/a&gt;
that others found helpful
so I figured I'd aggregate some of those tips here.
&lt;/p&gt;

&lt;h3 id="multiple-files"&gt;Operating on multiple files&lt;/h3&gt;

&lt;p&gt;
While most folks invoke
&lt;code&gt;less&lt;/code&gt;
at the tail of a pipeline like
&lt;figure&gt;
&lt;pre&gt;
&lt;label&gt;$ &lt;/label&gt; &lt;var&gt;command&lt;/var&gt; | less
&lt;/pre&gt;
&lt;figcaption&gt;Invoking &lt;code&gt;less&lt;/code&gt; in a pipeline&lt;/figcaption&gt;
&lt;/figure&gt;

you can directly provide one or more files to open

&lt;figure&gt;
&lt;pre&gt;
&lt;label&gt;$ &lt;/label&gt; less &lt;var&gt;README.txt file.c *.md&lt;/var&gt;
&lt;/pre&gt;
&lt;figcaption&gt;Invoking &lt;code&gt;less&lt;/code&gt; directly&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;/p&gt;

&lt;h3 id="adding-files"&gt;Adding files after starting&lt;/h3&gt;

&lt;p&gt;
When reading a document,
sometimes you want to view another file,
adding it to the file list.
Perhaps while reading some C source code
you want to also look over the corresponding header-file.
You can add that header-file to the argument list with
&lt;code&gt;:e file.h&lt;/code&gt;
&lt;/p&gt;

&lt;h3 id="navigating-multiple"&gt;Navigating multiple files&lt;/h3&gt;

&lt;p&gt;
You can navigate between multiple files using
&lt;code&gt;:n&lt;/code&gt;
to go to the next file in the argument-list, and
&lt;code&gt;:p&lt;/code&gt;
for the previous file.
You can also use
&lt;code&gt;:x&lt;/code&gt;
to rewind to the first file in the argument-list
similar to how
&lt;code&gt;:rewind&lt;/code&gt;
behaves in
&lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;.
&lt;/p&gt;

&lt;h3 id="removing-files"&gt;Removing files after starting&lt;/h3&gt;

&lt;p&gt;
While I rarely feel the need to,
if you have finished with a file
and want to keep your argument list clean,
you can use
&lt;code&gt;:d&lt;/code&gt;
to delete the current file from the argument-list.
&lt;/p&gt;

&lt;h2 id="navigating-within"&gt;Navigating&lt;/h2&gt;

&lt;h3 id="jump-to-line"&gt;Jumping to a particular line-number&lt;/h3&gt;

&lt;p&gt;
Use
&lt;code&gt;&lt;var&gt;«count»&lt;/var&gt;G&lt;/code&gt;
to jump to a particular line-number.
So using
&lt;code&gt;&lt;var&gt;3141&lt;/var&gt;G&lt;/code&gt;
will jump to line &lt;var&gt;3141&lt;/var&gt;.
It helps to display
&lt;a href="https://blog.thechases.com/posts/assorted-less-tips/#option-N"&gt;line numbers&lt;/a&gt;.
&lt;/p&gt;

&lt;h3 id="jump-to-percent"&gt;Jumping to a particular percentage-offset&lt;/h3&gt;

&lt;p&gt;
Similarly, using
&lt;code&gt;&lt;var&gt;«count»&lt;/var&gt;%&lt;/code&gt;
jumps to that percentage-offset of the file.
So if you want to go to ¾
of the way through the file, you can type
&lt;code&gt;75%&lt;/code&gt;
to jump right there.
&lt;/p&gt;

&lt;h3 id="searching"&gt;Searching&lt;/h3&gt;

&lt;p&gt;
While many folks know you can search forward with
&lt;code&gt;/&lt;var&gt;«pattern»&lt;/var&gt;&lt;/code&gt;
and some people know you can use
&lt;code&gt;?&lt;var&gt;«pattern»&lt;/var&gt;&lt;/code&gt;
to search backwards,
or use
&lt;code&gt;n&lt;/code&gt;/&lt;code&gt;N&lt;/code&gt;
to search again for the next/previous match,
&lt;code&gt;less&lt;/code&gt;
provides modifiers you can specify
before the pattern to modify its behavior:
&lt;/p&gt;

&lt;dl id="search-modifier"&gt;
 &lt;dt&gt;&lt;code&gt;!&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;
  Find the next line that
  &lt;strong&gt;doesn't&lt;/strong&gt;
  match the pattern
 &lt;/dd&gt;
 &lt;dt&gt;&lt;code&gt;*&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;
 search across multiple files,
 starting from the current location
 in the current file
 &lt;/dd&gt;
 &lt;dt&gt;&lt;code&gt;@&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;
 rewind to the first file
 and search from there
 &lt;/dd&gt;
 &lt;dt&gt;&lt;code&gt;@*&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;
 rewind to the first file
 and search from there
 across multiple files
 &lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;
Thus you would use
&lt;code&gt;/@*&lt;var&gt;«pattern»&lt;/var&gt;&lt;/code&gt;
to search for "pattern"
starting with the first file.
&lt;/p&gt;

&lt;h2 id="filtering"&gt;Filtering lines&lt;/h2&gt;

&lt;p&gt;
Using
&lt;code&gt;&amp;amp;&lt;/code&gt;
lets you specify a pattern
and filter the displayed lines
to only those matching the pattern,
much like an internal
&lt;code&gt;grep&lt;/code&gt;
command.
If you modify it with
&lt;code&gt;!&lt;/code&gt;,
so it will display only those lines that do
&lt;em&gt;not match&lt;/em&gt;
the pattern, like
&lt;code&gt;&amp;amp;!&lt;var&gt;«pattern»&lt;/var&gt;&lt;/code&gt;.
I find this particularly helpful for browsing log-files.
&lt;/p&gt;

&lt;h2 id="bookmarking"&gt;Bookmarking&lt;/h2&gt;
&lt;p&gt;
You can bookmark points in a file with
&lt;code&gt;m&lt;/code&gt;
followed by a letter,
then jump back to that bookmark with
&lt;code&gt;'&lt;/code&gt;
followed by the same letter.
These apply globally across all open files,
so if you
&lt;code&gt;ma&lt;/code&gt;
in the third file,
then navigate away to other files,
using
&lt;code&gt;'a&lt;/code&gt;
will take you back to the marked location
in that third file.
I use marks most when reading man-pages,
dropping one mark at the
&lt;tt&gt;OPTIONS&lt;/tt&gt;
section such as
&lt;code&gt;mo&lt;/code&gt;,
and another at the
&lt;tt&gt;EXAMPLES&lt;/tt&gt;
section,
such as
&lt;code&gt;me&lt;/code&gt;,
then bounce back and forth between them with
&lt;code&gt;'o&lt;/code&gt;
and
&lt;code&gt;'e&lt;/code&gt;.
While you can use any of
the 26 lowercase or uppercase letters
(for a total of 52 marks),
I rarely use more than two or three
either in alphabetical order
("a", "b", "c"),
or assigning mnemonics like in the
&lt;code&gt;man&lt;/code&gt;-page example above.
&lt;/p&gt;

&lt;h3 id="bracket-matching"&gt;Bracket matching&lt;/h3&gt;

&lt;p&gt;
If the first line on the screen contains a
&lt;code&gt;(&lt;/code&gt;,
&lt;code&gt;[&lt;/code&gt;,
or
&lt;code&gt;{&lt;/code&gt;,
typing that character
will jump to the matching/closing character,
putting it on the bottom line of the screen.
Similarly, if a closing
&lt;code&gt;)&lt;/code&gt;,
&lt;code&gt;]&lt;/code&gt;,
or
&lt;code&gt;}&lt;/code&gt;,
character appears on the last line,
typing that closing character
will jump to the matching/opening character,
putting it at the top of the screen.
I find it a little disorienting
if they fall &lt;code&gt;less&lt;/code&gt; than a screen-height apart
because what feels like a forward motion
to find the next matching close-bracket
might actually result in shifting the screen
&lt;em&gt;down&lt;/em&gt;
rather than
&lt;em&gt;up&lt;/em&gt;
which feels backwards.
&lt;/p&gt;

&lt;p&gt;
While I don't use it much,
you can also specify match-pairs using
&lt;code&gt;&lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;ctrl+f&lt;/kbd&gt;&lt;/code&gt;
or
&lt;code&gt;&lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;ctrl+b&lt;/kbd&gt;&lt;/code&gt;
followed by the opening/closing pair of characters
such as
&lt;code&gt;&lt;kbd&gt;alt&lt;/kbd&gt;+&lt;kbd&gt;ctrl+f&amp;lt;&amp;gt;&lt;/kbd&gt;&lt;/code&gt;
to define a "&amp;lt;"…"&amp;gt;" pair
and jump between them
in a manner similar to the
&lt;code&gt;(&lt;/code&gt;/&lt;code&gt;)&lt;/code&gt;,
&lt;code&gt;[&lt;/code&gt;/&lt;code&gt;]&lt;/code&gt;,
and
&lt;code&gt;{&lt;/code&gt;/&lt;code&gt;)&lt;/code&gt;
motions.
&lt;/p&gt;

&lt;h2 id="toggling-options"&gt;Toggling options without restarting&lt;/h2&gt;

&lt;p&gt;
While the
&lt;code&gt;man&lt;/code&gt;-page
documents many flags you can pass
on the command-line,
you can also toggle boolean options from inside
&lt;code&gt;less&lt;/code&gt;.
I find this particularly helpful
when I've fed the output of a long-running process to
&lt;code&gt;less&lt;/code&gt;
and don't want to re-run it
because it will take a long time.
Instead of quitting,
you can type a literal
&lt;code&gt;-&lt;/code&gt;
followed by the option you want to change.
I most commonly want to toggle word-wrap for long lines,
so instead of quitting and adding
&lt;code&gt;-S&lt;/code&gt;
at the end of my pipeline,
I can type
&lt;code&gt;-S&lt;/code&gt;
directly in
&lt;code&gt;less&lt;/code&gt;.
Options I commonly toggle:
&lt;/p&gt;&lt;dl id="options"&gt;
 &lt;dt id="option-S"&gt;&lt;code&gt;-S&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;word-wrap (mnemonic "splitting long lines")&lt;/dd&gt;
 &lt;dt id="option-G"&gt;&lt;code&gt;-G&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;search-highlighting&lt;/dd&gt;
 &lt;dt id="option-I"&gt;&lt;code&gt;-i&lt;/code&gt;/&lt;code&gt;-I&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;smart-case/case-sensitivity for searches&lt;/dd&gt;
 &lt;dt id="option-R"&gt;&lt;code&gt;-R&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;ANSI-color escaping&lt;/dd&gt;
 &lt;dt id="option-N"&gt;&lt;code&gt;-N&lt;/code&gt;/&lt;code&gt;-n&lt;/code&gt;&lt;/dt&gt;
 &lt;dd&gt;show/hide line-numbers&lt;/dd&gt;
&lt;/dl&gt;


&lt;h2 id="external-commands"&gt;Running external commands&lt;/h2&gt;

&lt;p&gt;
The
&lt;code&gt;!&lt;/code&gt;
lets you invoke an external command.
I don't do this often,
but occasionally I want some simple reference
like the current date
(&lt;code&gt;!date&lt;/code&gt;)
or to do some simple math
(&lt;code&gt;!bc&lt;/code&gt;).
&lt;/p&gt;

&lt;h2 id="env-vars"&gt;Default options with &lt;code&gt;$LESS&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;
You might find yourself regularly setting
a common group of
&lt;a href="https://blog.thechases.com/posts/assorted-less-tips/#options"&gt;options&lt;/a&gt;
so you can put those in your environment
(usually in your shell startup file like
&lt;code&gt;~/.bashrc&lt;/code&gt;)
like
&lt;code&gt;LESS="-RNe"&lt;/code&gt;
if you want to
&lt;a href="https://blog.thechases.com/posts/assorted-less-tips/#option-R"&gt;show ANSI colors&lt;/a&gt;,
&lt;a href="https://blog.thechases.com/posts/assorted-less-tips/#option-N"&gt;show line-numbers&lt;/a&gt;,
and exit automatically when you reach the end of the file.
&lt;/p&gt;

&lt;h2 id="other-misc"&gt;Other misc&lt;/h2&gt;

&lt;p&gt;
&lt;code&gt;less&lt;/code&gt;
has a few other corners that I've never really used,
but figured I'd document here:
&lt;/p&gt;

&lt;h3 id="tags"&gt;Tags&lt;/h3&gt;

&lt;p&gt;
While I've used tags in
&lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;
to easily jump between definitions.
However, even though
&lt;code&gt;less&lt;/code&gt;
provides support for tags generated by
&lt;code&gt;ctags&lt;/code&gt;.
I've never found cause to use them.
&lt;/p&gt;

&lt;h3 id="external-editor"&gt;Editing the current document&lt;/h3&gt;

&lt;p&gt;
The
&lt;code&gt;v&lt;/code&gt;
command will open your
&lt;code&gt;$VISUAL&lt;/code&gt;
editor on the current document.
&lt;/p&gt;

&lt;h3 id="log-output"&gt;"Log" output&lt;/h3&gt;

&lt;p&gt;
&lt;code&gt;less&lt;/code&gt;
lets you redirect the output
it has gathered from
&lt;abbr title="standard input"&gt;stdin&lt;/abbr&gt;
to a file using the
&lt;code&gt;o&lt;/code&gt;
command
(or the
&lt;code&gt;O&lt;/code&gt;
command to overwrite an existing file).
This might come in handy because
&lt;code&gt;less&lt;/code&gt;
won't let you
&lt;a href="https://blog.thechases.com/posts/assorted-less-tips/#external-editor"&gt;edit stdin
in an external editor&lt;/a&gt;
but you can write it directly to a file.
&lt;/p&gt;</description><category>cli</category><category>console</category><category>less</category><guid>https://blog.thechases.com/posts/assorted-less-tips/</guid><pubDate>Tue, 11 Nov 2025 15:28:50 GMT</pubDate></item><item><title>Blog Question Challenge 2025</title><link>https://blog.thechases.com/posts/blog-question-challenge-2025/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h3&gt;Why did you start blogging in the first place?&lt;/h3&gt;
&lt;p&gt;Part of me wants to just record my thought-process
and solidify my thinking.&lt;/p&gt;
&lt;p&gt;Another part like sharing things I learn
in case they help others.&lt;/p&gt;
&lt;p&gt;And sometimes I just want to rant.&lt;/p&gt;
&lt;h3&gt;What platform are you using to manage your blog, and why do you use it&lt;/h3&gt;
&lt;p&gt;In the late 90s,
I wrote raw HTML files
and just posted them on
the RedHat web-server
that my college CS department offered.
Good ol' table-based layout and all that.&lt;/p&gt;
&lt;p&gt;Once I got my own domain,
I looked around at
Static Site Generators (SSGs)
and &lt;a href="https://getnikola.com/"&gt;Nikola&lt;/a&gt;,
topped my list.
I wanted an SSG that&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;could ingest pure HTML fragments
    (which I prefer)
    rather than forcing me to use
    something like Markdown
    or AsciiDoc.
    I don't mind using them
    for casual prose (like this)
    but for technical work,
    I prefer the markup-control
    that I get from raw HTML&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;was written in Python
    (which I use as part of &lt;code&gt;$DAYJOB&lt;/code&gt;,
    so I felt more comfortable
    poking under the hood)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ran on all the platforms I used
    (Linux at the time, now
    &lt;a href="https://freebsd.org"&gt;FreeBSD&lt;/a&gt;,
    and
    &lt;a href="https://openbsd.org"&gt;OpenBSD&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;had metadata features that fit my requirements&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;had a built-in web-server
    for viewing the site locally&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;was easy to create an &lt;code&gt;rsync&lt;/code&gt; deploy hook
    to send my files up to my web-server&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, Nikola has a lot of churn,
meaning I frequently have to revisit
my configuration files
or regularly suffer the wrath of
warnings and errors
at every upgrade.
It also processes my HTML source through &lt;code&gt;lxml&lt;/code&gt;
which in turn mangles certain cromulent markup,
meaning my input doesn't show up in output.
And system upgrades of Python
seem to throw my virtual-env into a bit of a frenzy.&lt;/p&gt;
&lt;p&gt;So I've started a &lt;code&gt;Makefile&lt;/code&gt;-based SSG
that largely sticks to BSD &lt;code&gt;make&lt;/code&gt; and POSIX &lt;code&gt;awk&lt;/code&gt;.
Stay tuned when that comes to fruition.&lt;/p&gt;
&lt;h3&gt;How do you write your posts?&lt;/h3&gt;
&lt;p&gt;A plain ol' text editor.&lt;/p&gt;
&lt;p&gt;Sometimes &lt;code&gt;vi&lt;/code&gt;/&lt;code&gt;vim&lt;/code&gt;, sometimes &lt;code&gt;ed(1)&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Do you normally publish immediately after writing, or do you let it simmer a bit?&lt;/h3&gt;
&lt;p&gt;Most posts take me a while to compose,
so those get set to Draft status,
and the publishing process uploads the static files
but doesn't include them in indexing
or the RSS feed.
This allows me to read them on my site
(optionally sharing the pre-release URL with others)
and revise them accordingly.
However, once I feel it's done,
I go ahead and remove the Draft status
to publish it.&lt;/p&gt;
&lt;h3&gt;Have you blogged on other platforms before?&lt;/h3&gt;
&lt;p&gt;If you count "microblogging",
then I've blogged
&lt;a href="https://blog.thechases.com/twitter.com/Gumnos"&gt;on Twitter/X&lt;/a&gt;
and have largely moved my microblogging participation
&lt;a href="https://mastodon.bsd.cafe/@gumnos"&gt;to Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, for the most part, 
it's just been my own site/blog.&lt;/p&gt;
&lt;h3&gt;When do you feel most inspired to write?&lt;/h3&gt;
&lt;p&gt;A couple triggers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I'm working through something complex
    and want to document it for myself&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I've found a fun little shell trick
    and want to share it&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I've experienced some frustration
    and just need to rant&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;What’s your favorite post on your blog?&lt;/h3&gt;
&lt;p&gt;Probably 
&lt;a href="https://blog.thechases.com/posts/remind/"&gt;my post&lt;/a&gt;
on using
&lt;a href="https://dianne.skoll.ca/projects/remind/"&gt;&lt;code&gt;remind&lt;/code&gt;&lt;/a&gt;.
However, it's starting to show its age,
so I should revisit the article
and update it.&lt;/p&gt;
&lt;h3&gt;Any future plans for the blog?&lt;/h3&gt;
&lt;p&gt;Keep on emitting a steady trickle
of whatever interests me.&lt;/p&gt;</description><guid>https://blog.thechases.com/posts/blog-question-challenge-2025/</guid><pubDate>Thu, 16 Jan 2025 11:51:33 GMT</pubDate></item><item><title>The fall of the User Agent</title><link>https://blog.thechases.com/posts/fall-of-the-user-agent/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2 id="in-the-beginning"&gt;In the beginning&lt;/h2&gt;

&lt;p&gt;
Similar to some
Mail User Agents (MUAs)
and
&lt;a href="https://datatracker.ietf.org/doc/html/rfc5536#section-3.2.13"&gt;&lt;abbr title="Network News Transfer Protocol"&gt;NNTP&lt;/abbr&gt;&lt;/a&gt;
clients,
the 
specification for HTTP
&lt;a href="https://www.w3.org/Protocols/HTTP/HTRQ_Headers.html#user-agent"&gt;since
at least version 0.9&lt;/a&gt;
have included
&lt;a href="https://en.wikipedia.org/wiki/User-Agent_header"&gt;&lt;code&gt;User-Agent&lt;/code&gt;&lt;/a&gt;
headers that still exist
in modern
&lt;a href="https://www.rfc-editor.org/rfc/rfc1945#section-10.15"&gt;HTTP&lt;/a&gt;
standards.
&lt;/p&gt;

&lt;p&gt;
This header lets the server know
what software made the request.
But it also provides a reminder
that the software existed
to act on behalf of the
&lt;strong&gt;user&lt;/strong&gt;.
&lt;/p&gt;

&lt;h2 id="abuse"&gt;Abuse&lt;/h2&gt;

&lt;p&gt;
Sadly, server-side software
started to abuse the
&lt;code&gt;User-Agent&lt;/code&gt;
header.
Based on the value,
a web-server would respond with different output
depending on what it thought the
other side could handle.
By accommodating a broken client
and making presumptions about how it would behave,
this
&lt;code&gt;User-Agent&lt;/code&gt;
sniffing led to a fractured web.
&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;

&lt;p&gt;
&lt;/p&gt;</description><guid>https://blog.thechases.com/posts/fall-of-the-user-agent/</guid><pubDate>Mon, 23 Dec 2024 16:30:00 GMT</pubDate></item><item><title>Lowe's rant</title><link>https://blog.thechases.com/posts/rants/lowes1/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
Sigh.
Lowe's doesn't seem to be able to competently
ship a part that still shows as in-stock on their site.
Rant documenting the drama follows.
&lt;/p&gt;

&lt;p&gt;
&lt;abbr title="Too long; didn't read"&gt;tl;dr&lt;/abbr&gt;
order online,
they cancel.
order online again,
they cancel.
Call support,
get told to place the order with the local store.
Call local store,
get told they can't.
Call support,
get told to physically go into the store to order.
Order gets placed.
Order arrives,
installer notes it's missing parts.
Call support,
get told they'll rush out a replacement.
Replacement arrives.
Check box,
still missing same parts.
Call support,
rush delivery of
&lt;em&gt;another&lt;/em&gt;
replacement with notes for the shipper to check the contents.
Third order arrives
with the same contents
as the previous two.
To be continued…
&lt;/p&gt;

&lt;!-- TEASER_END --&gt;

&lt;h2 id="round1"&gt;Round 1&lt;/h2&gt;

&lt;h3&gt;2024-06-02 at 20:10&lt;/h3&gt;

&lt;p&gt;
Great.
They have the faucet we want
on their website.
Stainless steel finish,
high-arc,
with sprayer.
While the brushed-nickel finish
shows as out-of-stock,
this one isn't.
Order it for $172.10 plus tax.
Receive order confirmation email.
&lt;/p&gt;

&lt;h3&gt;2024-06-03 at 16:12&lt;/h3&gt;
&lt;blockquote&gt;
Your order has been canceled.
You won't be charged.
Questions?
Call customer care at
1-800-445-6937
&lt;cite&gt;email from Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;h2 id="round2"&gt;Round 2&lt;/h2&gt;

&lt;h3&gt;2024-06-03 at 20:57&lt;/h3&gt;

&lt;p&gt;
Strange.
Something must have gone wrong.
But glad we weren't charged.
Back to the website.
Still available,
still in-stock,
still everything we need.
Okay, fine.
Reorder it for $172.10 plus tax.
&lt;/p&gt;

&lt;h3&gt;2024-06-04 at 08:50&lt;/h3&gt;
&lt;blockquote&gt;
Your order has been canceled.
You won't be charged.
Questions?
Call customer care at
1-800-445-6937
&lt;cite&gt;email from Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;p&gt;
Okay, this is annoying.
Time to call customer service.
&lt;/p&gt;

&lt;h2 id="round3"&gt;Round 3&lt;/h2&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Lowe's customer service phone-call&lt;/cite&gt;

&lt;blockquote class="me"&gt;
Hey, my online order keeps getting cancelled.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
That
&lt;em&gt;is&lt;/em&gt;
strange.
It shows as in-stock,
but my system won't let me place this order,
so you'll have to talk to your local store
and have them order the part
because they use a different ordering system.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Strange.
Okay, I'll call them.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;/div&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Call with our local Lowe's&lt;/cite&gt;

&lt;blockquote class="me"&gt;
Hey,
[recounts saga thus far]
I was told I needed to have
my local store place this order
because of some system issue.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
I'm sorry,
we can't order that over the phone.
You'll have to order it online.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
But they told me
I had to talk to you!
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
I'm sorry,
you'll have to call them back then.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Fine.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;/div&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Lowe's corporate customer service phone-call&lt;/cite&gt;

&lt;blockquote class="me"&gt;
[recounts saga thus far]
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
Ah, you need to go
&lt;strong&gt;in person&lt;/strong&gt;
to the local store to place the order,
not over the phone.
They can place the order
and get a manager-override if needed.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
That's…disappointing.
Thanks, I'll do that.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;
&lt;/div&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Local Lowe's customer service desk in person&lt;/cite&gt;

&lt;blockquote class="me"&gt;
[recounts saga thus far]
Can we order this part
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
Sure!
[confirms part-number, description, and other details]
That will be $169.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Thanks, and that's less than what it was
when I tried to order online.
At least there's some sunshine here.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;/div&gt;

&lt;p&gt;
A few days later,
the box shows up on time.
I send the details to our handiman
and schedule him to come out
after we've returned from a family trip.
&lt;/p&gt;

&lt;h3&gt;2024-07-12&lt;/h3&gt;

&lt;p&gt;
Our handiman comes out,
does a quick check that we have
our new basin &amp;amp; faucet,
and starts replacing
them as requested.
He goes to install the faucet,
raises an eyebrow,
asking if I'd ordered one with a sprayer
like we'd discussed.
I check my order confirmation email.
Yep, faucet &amp;amp; sprayer in stainless steel.
He shows me the contents of the box.
No sprayer,
no sprayer hook-up line coming from the faucet assembly.
Looks like it's a brushed nickel finish, too,
not the stainless steel finish we'd ordered.
Sigh.
We agree to install the faucet that we do have
just so we're not without a kitchen sink
indefinitely.
&lt;/p&gt;

&lt;p&gt;
Time to contact Lowe's customer support again.
&lt;/p&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Lowe's customer service&lt;/cite&gt;

&lt;blockquote class="me"&gt;
[recounts saga thus far, providing the order number]
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
Great, I have that order pulled up.
It looks like it was for a
stainless steel
high-arc faucet
with sprayer.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Funny you should read that description to me
because what was shipped did
&lt;em&gt;not&lt;/em&gt;
include a sprayer,
and appears to be a brushed nickel faucet.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
That's not good,
I'm sorry, that must be frustrating.
You can return that one to your local store,
and I'll ship out a replacement.
If you'll provide your credit-card info,
that will be $153.90 [yet a 3rd price?]
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Okay, that's additional frustration,
because now I have two faucets purchased,
but I can't return the first one
until the second one arrives.
But at least it's still cheaper
than previous attempts.
&lt;br&gt;
Let's go ahead.
[provides CC details]
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;/div&gt;

&lt;p&gt;
The shipment arrives in a mere two days,
on a Sunday at that.
So I open it to check,
only to find that it's
&lt;strong&gt;exactly the same kit&lt;/strong&gt;
I received previously.
No sprayer,
no sprayer hookup
and wrong finish.
Immediately back on the phone
with customer support.
&lt;/p&gt;

&lt;h2 id="round4"&gt;Round 4&lt;/h2&gt;

&lt;div class="conversation"&gt;
&lt;cite&gt;Lowe's customer service&lt;/cite&gt;

&lt;blockquote class="me"&gt;
[recounts saga thus far,
providing the most recent order number]
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
Great, I have that order pulled up.
It looks like it was for a
stainless steel
high-arc faucet
with sprayer.
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Funny you should read that description to me
because what was shipped did
&lt;em&gt;not&lt;/em&gt;
include a sprayer,
and appears to be a brushed nickel faucet.
And this is the
&lt;strong&gt;second time&lt;/strong&gt;
this has happened.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="them"&gt;
That's
&lt;em&gt;really&lt;/em&gt;
not good,
I'm sorry, that must be frustrating.
You can return that one to your local store,
and I'll ship out a replacement.
I'll also place a note
in the shipping instructions
to have them verify
that it has the sprayer,
hookups,
and is the right finish
&lt;cite&gt;Lowe's&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;blockquote class="me"&gt;
Thanks so much.
&lt;cite&gt;me&lt;/cite&gt;
&lt;/blockquote&gt;

&lt;/div&gt;

&lt;p&gt;
Pleasantly surprised they didn't seem
to charge me
&lt;em&gt;yet again&lt;/em&gt;
for this round.
We shall see.
&lt;/p&gt;

&lt;h3&gt;2024-07-17&lt;/h3&gt;

&lt;p&gt;
The shipment arrives.
I open it up.
&lt;strong&gt;SURPRISE!&lt;/strong&gt;
No, not really.
It's the same item.
I now have
&lt;strong&gt;three&lt;/strong&gt;
of the same hardware kit.
Time to go back to the store in person,
return these two wrong bits,
get my money back,
and obtain some degree
of competency in the matter.
&lt;/p&gt;

&lt;p&gt;To be continued…&lt;/p&gt;</description><guid>https://blog.thechases.com/posts/rants/lowes1/</guid><pubDate>Sun, 14 Jul 2024 19:14:30 GMT</pubDate></item><item><title>Exploring ActivityPub/ActivityStreams</title><link>https://blog.thechases.com/posts/activitypub-activitystreams/</link><dc:creator>Tim Chase</dc:creator><description>&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;

&lt;p&gt;
 Wanting to dig into
 &lt;a href="https://www.w3.org/TR/activitypub/"&gt;ActivityPub&lt;/a&gt;
 /
 &lt;a href="https://www.w3.org/TR/activitystreams-core/"&gt;ActivityStreams&lt;/a&gt;
 for a while
 I figured I'd read the specs and dive in.
 Having read plenty of
 &lt;a href="https://en.m.wikipedia.org/wiki/Request_for_Comments" title="Request for Comment"&gt;RFC&lt;/a&gt;
 specs before,
 I figured this wouldn't take too long.
 Boy was I wrong.
&lt;/p&gt;

&lt;p&gt;
 This article is currently a
 &lt;abbr title="Work In Progress"&gt;WIP&lt;/abbr&gt;
 so it may get updated
 multiple times as I learn more
 and read deeper into the various specs/RFCs
 and their sub-dependencies.
&lt;/p&gt;
&lt;!-- TEASER_END --&gt;

&lt;p&gt;
 The whole family of specs
 feels like the authors wanted a grab-bag
 of front-end features
 rather than detailing the protocols
 with the precision of engineers that write
 &lt;abbr title="Request for Comments"&gt;RFCs&lt;/abbr&gt;.
 The observations that follow
 document some of my frustrations along the way.
 Sorry if this comes across a little more ranty &amp;amp; grumbling
 compared to my usual posts.
&lt;/p&gt;

&lt;p&gt;
 On top of all the
 &lt;em&gt;standardized&lt;/em&gt;
 mess,
 &lt;a href="https://docs.joinmastodon.org/spec/activitypub/"&gt;Mastodon
 adds its own layer of non-standardized attributes&lt;/a&gt;
 that other ActivityPub software
 is expected to understand.
 &lt;!--
 Note to self: Mastodon uses the summary: attribute as the CW.
 If the Object has a content: attribute, use that;
 otherwise use the name: attribute

 --&gt;
&lt;/p&gt;

&lt;h2 id="activitypub1"&gt;ActivityPub (part 1)&lt;/h2&gt;

&lt;p&gt;
 Upon embarking,
 the first thing I learned was that
 &lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt;,
 &lt;a href="https://gotosocial.org/"&gt;GoToSocial&lt;/a&gt;,
 &lt;a href="https://pleroma.social/"&gt;Pleroma&lt;/a&gt;,
 &lt;a href="https://pixelfed.org/"&gt;PixelFed&lt;/a&gt;,
 &lt;a href="https://joinpeertube.org"&gt;PeerTube&lt;/a&gt;,
 and other distributed-web services
 all run on a backbone of
 &lt;a href="https://www.w3.org/TR/activitypub/"&gt;ActivityPub&lt;/a&gt;
 so I figured I'd start there.
&lt;/p&gt;

&lt;p&gt;
 The ActivityPub spec clocks in
 at about 36 pages when printed.
 Pretty manageable.
&lt;/p&gt;

&lt;p&gt;
 The very first thing you read there?
 &lt;/p&gt;&lt;blockquote&gt;
  The ActivityPub protocol
  is a decentralized social networking protocol
  based upon the
  &lt;a href="https://www.w3.org/TR/activitystreams-core/"&gt;ActivityStreams&lt;/a&gt;
  2.0 data format.
  It provides a client to server API
  for creating, updating and deleting content,
  as well as a federated server to server API
  for delivering notifications and content.
 &lt;/blockquote&gt;
 Okay, I guess I need to read the ActivityStreams spec first.


&lt;h2 id="activitystreams1"&gt;ActivityStreams (part 1)&lt;/h2&gt;

&lt;p&gt;
 Okay, the ActivityStreams spec adds
 roughly another 46 pages of reading.
&lt;/p&gt;

&lt;p&gt;
 ActivityStreams consist of
 &lt;a href="https://json.org" title="JavaScript Object Notation"&gt;JSON&lt;/a&gt;
 data,
 formally defined as an
 &lt;a href="https://datatracker.ietf.org/doc/html/rfc7159"&gt;RFC-7159&lt;/a&gt;.
 Fortunately, I'm reasonably familiar with JSON
 so I won't go into that here.
&lt;/p&gt;

&lt;p&gt;
 However, barely into the second section
 and we discover that we need to learn about
 &lt;abbr title="JavaScript Object Notation: Linked Data"&gt;JSON-LD&lt;/abbr&gt;
 (another roughly 200 pages).
&lt;/p&gt;

&lt;h2 id="jsonld1"&gt;JSON-LD part 1&lt;/h2&gt;

&lt;p&gt;
 Based on the spec,
 the web-server should serve
 JSON-LD documents with a
 &lt;abbr title="Multipurpose Internet Mail Extensions"&gt;MIME&lt;/abbr&gt;
 type of
 &lt;tt class="term"&gt;application/json&lt;/tt&gt;.
&lt;/p&gt;

&lt;p&gt;
 And one of the first things we learn?
 There's a relationship with
 &lt;abbr title="Resource Description Framework"&gt;RDF
 so time to take a detour
 to learn about
 &lt;a href="https://www.w3.org/TR/rdf11-concepts/"&gt;RDF concepts&lt;/a&gt;.
&lt;/abbr&gt;&lt;/p&gt;

&lt;h2 id="rdf1"&gt;RDF (part 1?)&lt;/h2&gt;

&lt;p&gt;
 Goody, another 41 pages of reading.
 With cross-references to
 othe things like
 RDF-Schema.
 I'll defer digging into that for now.
&lt;/p&gt;

&lt;p&gt;
 The general gist is that
 &lt;abbr title="Resource Description Framework"&gt;RDF&lt;/abbr&gt;
 specifies things in terms of
 Subject→Predicate→Object.
 The Subject may be
 an &lt;abbr title="Internationalized Resource Identifier"&gt;IRI&lt;/abbr&gt;,
 or a "blank node"
 (but
 &lt;strong&gt;not&lt;/strong&gt;
 a literal).
 The Object may be
 an IRI,
 a "blank node",
 or a literal.
 See &lt;a href="https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple"&gt;further documentation&lt;/a&gt;.
 Why can an Object
 be any of them,
 but a Subject can't be a Literal?
 Kinda glossed over.
&lt;/p&gt;

&lt;h2 id="activitystreams2"&gt;ActivityStreams (part 2)&lt;/h2&gt;

&lt;p&gt;
 Popping from the stack,
 we return to ActivityStreams.
 Note that JSON-LD has a
 &lt;abbr title="Multipurpose Internet Mail Extensions"&gt;MIME&lt;/abbr&gt;
 type of
 &lt;tt class="term"&gt;application/json&lt;/tt&gt;
 while ActivityStreams specifies
 &lt;tt class="term"&gt;application/activity+json&lt;/tt&gt;
 so we already have conflicting information.
&lt;/p&gt;

&lt;p&gt;
 However, before getting a paragraph into
 &lt;a href="https://www.w3.org/TR/activitystreams-core/#syntaxconventions"&gt;Serialization&lt;/a&gt;
 we learn the spec defines an entire
 &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/"&gt;ActivityStreams vocabulary&lt;/a&gt;
 so time for another detour.
&lt;/p&gt;

&lt;h2 id="activitystreamsvocab1"&gt;ActivityStreams Vocabulary (part 1)&lt;/h2&gt;

&lt;p&gt;
 The vocabulary definition clocks in
 at roughly another 54 pages.
 For those playing along at home,
 we're up to 200+36+46+54=336 pages
 without counting
 &lt;abbr title="JavaScript Object Notation"&gt;JSON&lt;/abbr&gt;
 specifications or the
 &lt;a href="http://xmlns.com/foaf/spec/" title="Friend of a Friend"&gt;FoaF&lt;/a&gt;
 &amp;amp;
 &lt;a href="http://vocab.org/relationship/"&gt;Relationship&lt;/a&gt;
 specs referenced by the ActivityPub Vocabulary document.
&lt;/p&gt;

&lt;p&gt;
 Looking over this document,
 the particulars will require knowing more
 about ActivityPub/ActivityStreams
 so I'll return to this later.
&lt;/p&gt;

&lt;h2 id="activitystreams3"&gt;ActivityStreams (part 3)&lt;/h2&gt;

&lt;h3 id="activitystreamsgood"&gt;The Good&lt;/h3&gt;

&lt;dl&gt;

 &lt;dt&gt;Encoding&lt;/dt&gt;
 &lt;dd&gt;
  The specification clearly states
  that all data gets serialized as
  &lt;a href="https://en.wikipedia.org/wiki/UTF-8"&gt;UTF-8&lt;/a&gt;.
  This is good.
  I've encountered too many file-formats
  where this doesn't get defined
  and I have to guess the encoding.
  It's nice to be able to identify non-UTF8 text
  and reject malformed requests
  without having to retry some other encoding.
 &lt;/dd&gt;

 &lt;dt&gt;Date formats&lt;/dt&gt;
 &lt;dd&gt;
  Additionally, the spec clearly defines
  &lt;a href="https://www.w3.org/TR/activitystreams-core/#dates"&gt;date formats&lt;/a&gt; 
  according to
  &lt;a href="https://datatracker.ietf.org/doc/html/rfc3339"&gt;RFC-3339&lt;/a&gt;.
  However, I find the
  "time-offset isn't a time-zone" 
  mildly annoying.
 &lt;/dd&gt;

&lt;/dl&gt;

&lt;h3 id="activitystreamsbad"&gt;The Bad&lt;/h3&gt;

&lt;dl&gt;
 &lt;dt&gt;Context data-type&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   What is the data-type of a
   &lt;tt class="term"&gt;@context&lt;/tt&gt;'s
   value?
   According to the
   &lt;a href="https://www.w3.org/TR/json-ld11/#dfn-context"&gt;JSON-LD spec&lt;/a&gt;

   &lt;/p&gt;&lt;blockquote&gt;
    …the value of a term definition
    can either be a simple String, mapping the term to an
    &lt;abbr title="Internationalized Resource Identifier"&gt;IRI,
    or a map.
   &lt;/abbr&gt;&lt;/blockquote&gt;


   In the case of an ActivityStream,
   the String form has the fixed
   &lt;abbr title="Uniform Resource Indicator"&gt;URI&lt;/abbr&gt;
   value of
   "https://www.w3.org/ns/activitystreams".
   In the case of an object/map value,
   the document's type resides
   in the
   &lt;tt class="term"&gt;@vocab&lt;/tt&gt;
   sub-node along with other attributes
   such as the
   &lt;tt class="term"&gt;@language&lt;/tt&gt;
   or namespace definitions.
  

  &lt;p&gt;
   Except when it's a list.
   Wait, the JSON-LD spec clearly said
   &lt;q&gt;a simple String…or a map.&lt;/q&gt;
   Nothing about a list.
   Yet the JSON-LD spec itself
   &lt;a href="https://www.w3.org/TR/json-ld11/#example-20-describing-disconnected-nodes-with-graph"&gt;uses lists as values&lt;/a&gt;
   for the
   &lt;tt class="term"&gt;@context&lt;/tt&gt;.
  &lt;/p&gt;

  &lt;p&gt;
   Fine.
   It can be a list.
   Is it a list of homogeneous elements?
   Of course not.
   &lt;em&gt;Each element&lt;/em&gt;
   can be either a fixed String
   or a JSON object/mapping.
  &lt;/p&gt;

  &lt;p&gt;
   So how do you find the document type?
   Maybe it's the URI String found at
   &lt;tt class="term"&gt;.@context&lt;/tt&gt;
   or at
   &lt;tt class="term"&gt;.@context.@vocab&lt;/tt&gt;
   or at
   &lt;tt class="term"&gt;.@context[0]&lt;/tt&gt;
   or at
   &lt;tt class="term"&gt;.@context[0].@vocab&lt;/tt&gt;
   or maybe someplace else.
   Could it be 
   &lt;tt class="term"&gt;.@context[1]&lt;/tt&gt;
   instead?
   The specs don't have much to say about that.
  &lt;/p&gt;

  &lt;p&gt;
   But the specs
   &lt;em&gt;could&lt;/em&gt;
   have mandated a single way to do it,
   declaring that it's a list of objects,
   each object with a
   &lt;tt class="term"&gt;@vocab&lt;/tt&gt;
   property,
   and that the first one is the document-type.
   One place to look.
   But no.
  &lt;/p&gt;

  &lt;/dd&gt;

 &lt;dt&gt;What type of value should you expect?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   According to
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#object"&gt;section 4.1&lt;/a&gt;
   &lt;/p&gt;&lt;blockquote&gt;
    In addition to the global identifier
    (expressed as an absolute IRI using the
    &lt;tt class="term"&gt;id&lt;/tt&gt;
    property)
    and an "object type"
    (expressed using the
    &lt;tt class="term"&gt;type&lt;/tt&gt;
    property),
    all instances of the
    &lt;tt class="term"&gt;Object&lt;/tt&gt;
    type share a common set of properties
   &lt;/blockquote&gt;
   Okay, so Actors &amp;amp; Objects
   should have a mapping as a value,
   and that mapping should contain
   at least 
   &lt;tt class="term"&gt;id&lt;/tt&gt;
   &amp;amp;
   &lt;tt class="term"&gt;type&lt;/tt&gt;
   attributes.
   Except they don't have to:
   &lt;blockquote&gt;
    All properties are optional
    (including the
    &lt;tt class="term"&gt;id&lt;/tt&gt;
    &amp;amp;
    &lt;tt class="term"&gt;type&lt;/tt&gt;).
   &lt;/blockquote&gt;
   Would it hurt to make those required?
   But even worse, according to
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#examples"&gt;Example 4&lt;/a&gt;
   it looks like those values can be strings
   instead of maps/objects.
   They appear to be
   &lt;abbr title="Uniform Resource Indicator"&gt;URIs&lt;/abbr&gt;
   but it's not documented.
   So if you get a String here,
   all bets are off.
  
 &lt;/dd&gt;

 &lt;dt&gt;Links suffer the same value-type issue&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Similar to above,
   a Link like an
   &lt;tt class="term"&gt;image&lt;/tt&gt;
   can have
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-to-reference-a-single-image-without-any-additional-metadata-a-direct-association-can-be-expressed-as-a-json-String-containing-an-absolute-iri.x"&gt;a String&lt;/a&gt;
   as the value,
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-alternatively-if-additional-metadata-is-required-such-as-the-mime-content-type-of-the-referenced-resource-a-link-can-be-used"&gt;an Object&lt;/a&gt;
   as the value, or
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-example-14-context-https-www.w3.org-ns-activitystreams-type-application-id-http-example.org-application-123-name-exampletron-3000-image-http-example.org-application-abc.gif-type-link-href-http-example.org-application-123.png-mediatype-image-png-rel-thumbnail"&gt;a list&lt;/a&gt;.
   And the list can be composed of heterogeneous types,
   strings, objects, and maybe lists?
   Who knows?
   It's not clearly defined anywhere.
   This could have been defined once
   as a list of objects,
   and everything would fit.
   But no.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt id="hierarchy-inversion"&gt;Hierarchy inversion&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   An
   &lt;tt class="term"&gt;IntransitiveActivity&lt;/tt&gt;
   has an Actor
   but no Object.
   But the ActivityStreams spec
   defines defines an
   &lt;tt class="term"&gt;IntransitiveActivity&lt;/tt&gt;
   as
   &lt;/p&gt;&lt;blockquote&gt;
    specializations of the Activity type
    that represent intransitive actions.
   &lt;/blockquote&gt;
   This means from an object-oriented perspective
   an
   &lt;tt class="term"&gt;IntransitiveActivity&lt;/tt&gt;
   is a sub-class of an Activity.
   However this means that Activity objects
   have an Object,
   but their 
   &lt;tt class="term"&gt;IntransitiveActivity&lt;/tt&gt;
   sub-classes don't.
  
 &lt;/dd&gt;

 &lt;dt&gt;Type of &lt;tt class="term"&gt;type&lt;/tt&gt;&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Normally an object has a single
   &lt;tt class="term"&gt;type&lt;/tt&gt;
   value,
   a String representing the type of the object.
   Cool.
   But sometimes instead of a String,
   the
   &lt;tt class="term"&gt;type&lt;/tt&gt;
   can be a
   &lt;strong&gt;list&lt;/strong&gt;
   of types.
   Again, are the entries in the list homogeneous?
   Nope.
   Composed of strings and objects.
   Can a list-entry be another list?
   Who knows.
   Check out
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-an-activity-with-a-person-actor-extended-with-vcard-properties"&gt;Example 22&lt;/a&gt;
   to see this abomination in play.
   And with disjoint types,
   you can end up with redundant data,
   the same value in multiple keys.
   It's not like this is a specification
   requiring precision or anything.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;How big is a &lt;tt class="term"&gt;Collection&lt;/tt&gt;?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   A
   &lt;tt class="term"&gt;Collection&lt;/tt&gt;
   and its sub-classes
   (&lt;tt class="term"&gt;OrderedCollection&lt;/tt&gt;,
   &lt;tt class="term"&gt;CollectionPage&lt;/tt&gt;,
   and
   &lt;tt class="term"&gt;OrderedCollectionPage&lt;/tt&gt;)
   have both a
   &lt;tt class="term"&gt;totalItems&lt;/tt&gt;
   property and an
   &lt;tt class="term"&gt;items&lt;/tt&gt;
   property.
   But there are no requirements
   that these be disjoint.
   This means that you can have
   both properties
   and they can conflict.
   You could have a list of
   &lt;tt class="term"&gt;items&lt;/tt&gt;
   with 5 elements in it,
   yet have the
   &lt;tt class="term"&gt;totalItems&lt;/tt&gt;
   report 3 or 7 elements.
   Which should be displayed?
   It's not in the spec.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Pagination of an (unordered) &lt;tt class="term"&gt;Collection&lt;/tt&gt;&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If a collection is unordered,
   pagination through it makes
   &lt;strong&gt;zero sense&lt;/strong&gt;.
   Pagination
   &lt;em&gt;requires&lt;/em&gt;
   ordering.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Should you expect a String vs. a map?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The spec describes
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#naturalLanguageValues"&gt;natural language values&lt;/a&gt;
   as a way to detect whether you should expect
   a String-value
   or a map-value.
   So if you use the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   attribute, you get a String;
   and if you use the
   &lt;tt class="term"&gt;nameMap&lt;/tt&gt;
   attribute, you get a map/object.
   Not too bad.
   But why not keep that consistent
   across all the fields (above)
   that are sometimes a String
   and sometimes a map?
   Like that
   &lt;tt class="term"&gt;@context&lt;/tt&gt;
   attribute.
   Why not a
   &lt;tt class="term"&gt;@contextMap&lt;/tt&gt;
   then?
   But why start with consistency now?
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Which language wins?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If you specify the
   &lt;tt class="term"&gt;@context&lt;/tt&gt;
   as an object,
   you can include a
   &lt;tt class="term"&gt;@language&lt;/tt&gt;
   property to specify the default language
   of the object.
   Alternatively, you can specify various languages
   in certain
   &lt;tt class="term"&gt;*Map&lt;/tt&gt;
   attributes such as
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-multiple-language-specific-values"&gt;Example 15&lt;/a&gt;.
   However, it also specifies
   &lt;/p&gt;&lt;blockquote&gt;
    The special language tag
    "&lt;tt class="term"&gt;und&lt;/tt&gt;"
    [undefined]
    can be used within the object form
    to explicitly identify a value
    whose language is unknown or undetermined.
   &lt;/blockquote&gt;
   So if you've specified a default language using
   &lt;tt class="term"&gt;@language&lt;/tt&gt;,
   what language should
   "&lt;tt class="term"&gt;und&lt;/tt&gt;"
   text be rendered as?
  
 &lt;/dd&gt;

 &lt;dt&gt;Markup? What flavor is it?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Some values
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#h-biditext"&gt;contain markup&lt;/a&gt;.
   Some values don't.
   The
   &lt;tt class="term"&gt;content&lt;/tt&gt;
   &amp;amp;
   &lt;tt class="term"&gt;summary&lt;/tt&gt;
   do;
   the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/#dfn-name"&gt;doesn't&lt;/a&gt;.
   Which other fields do?
   Who knows.
   It's not well documented.
   What markup flavor?
   There are hints that markup in the
   &lt;tt class="term"&gt;content&lt;/tt&gt;
   field is
   &lt;abbr title="Hypertext Markup Language"&gt;HTML&lt;/abbr&gt;,
   But is that explicitly required?
   And if it is,
   what DocType?
   HTML5?
   HTML4.01?
   XHTML?
   Why not use Markdown?
  &lt;/p&gt;

  &lt;p&gt;
   As we'll discover later,
   ActivityPub specifies a
   &lt;tt class="term"&gt;source&lt;/tt&gt;
   attribute/extension
   that can have any flavor of source markup
   contingent on the
   &lt;tt class="term"&gt;mediaType&lt;/tt&gt;
   attribute.
   That then gets converted to the markup in the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   (or whatever other fields support markup).
   But that's a level higher than ActivityStreams
   which we're reading about here.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;HTML markup part Ⅱ&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Additionally, if the content is HTML,
   this makes it easy to bypass user-agent filters.
   If I want to filter out posts containing "emacs"
   but someone posts
   &lt;code&gt;em&amp;lt;span&amp;gt;a&amp;lt;/span&amp;gt;cs&lt;/code&gt;,
   a simple filter can't find "emacs" in there.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;HTML markup part Ⅲ&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Furthermore, inline
   &lt;abbr title="Cascading Style Sheets"&gt;CSS&lt;/abbr&gt;
   can trigger strange effects
   if not sanitized.
   A
   &lt;tt class="term"&gt;content&lt;/tt&gt;
   value containing something like
   &lt;code&gt;&amp;lt;span style="position:absolute;
   left:0; top:0;
   width:100vw; height:100vh;
   background-color:black; color:red"&amp;gt;Hah!&amp;lt;/span&amp;gt;&lt;/code&gt;
   can throw off the rendering of the whole interface.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;HTML markup part Ⅳ&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   How about other markup concerns like
   &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;
   input in your posts?
   Should this be allowed?
   How should it be sanitized?
   🤷 &lt;!-- shrug --&gt;
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;HTML markup part Ⅴ&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If we're sticking arbitrary blobs of HTML
   in fields such as the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   what security considerations have been taken into effect?
   What happens if some
   &lt;code&gt;&amp;lt;script&amp;gt;nefarious code&amp;lt;script&amp;gt;&lt;/code&gt;
   shows up in a field's value?
   If improperly sanitized,
   it can still allow &amp;lt;script&amp;gt; tags through.
   The spec is notably silent on these issues.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Security handwaving&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   In the section on
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#security-considerations"&gt;Security Considerations&lt;/a&gt;,
   the spec advises consumers to take care
   with malicious user-input
   and when re-emitting ingested content.
   What sort of care?
   🦗 &lt;!-- cricket --&gt;
   And the exhortation to beware
   of potential spoofing attacks?
   What assurances does the spec have
   for determining the integrity
   of an ActivityStreams?
   None.
   We'll revisit this when we get to ActivityPub
   where there's some effort here,
   but it hasn't been standardized.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Privacy handwaving&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Similar to the Security issue, the
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#privacy"&gt;Privacy Considerations&lt;/a&gt;
   section does a lot of handwaving.
   There's no standardization of users
   or audience-groups
   vs. public
   vs. private
   postings.
   The spec talks of "opting in" to disclosure of posts,
   but doesn't detail how.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;HTML markup part Ⅲ&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If we're sticking arbitrary blobs of HTML
   in fields such as the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   what security considerations have been taken into effect?
   What happens if some
   &lt;code&gt;&amp;lt;script&amp;gt;nefarious code&amp;lt;/script&amp;gt;&lt;/code&gt;
   shows up in a field's value?
   If improperly sanitized,
   it can still allow &amp;lt;script&amp;gt; tags through.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Security handwaving&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   In the section on
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#security-considerations"&gt;Security Considerations&lt;/a&gt;,
   the spec advises consumers to take care
   with malicious user-input
   and when re-emitting ingested content.
   What sort of care?
   :crickets:
   And the exhortation to beware
   of potential spoofing attacks?
   What assurances does the spec have
   for determining the integrity
   of an ActivityStreams?
   None.
   We'll revisit this when we get to ActivityPub
   where there's some effort here,
   but it hasn't been standardized.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Privacy handwaving&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Similar to the Security issue,
   the 
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#privacy"&gt;Privacy Considerations&lt;/a&gt;
   section does a lot of handwaving.
   There's no standardization of users
   or audience-groups
   vs. public
   vs. private
   postings.
   The spec talks of "opting in" to disclosure of posts,
   but doesn't detail how.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Namespaces vs. things in that namespace&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   A
   &lt;tt class="term"&gt;@context&lt;/tt&gt;
   can have a map as a value,
   including attributes that don't begin with an
   &lt;tt class="term"&gt;@&lt;/tt&gt;
   mapping to a 
   &lt;abbr title="Uniform Resource Indicator"&gt;URI&lt;/abbr&gt;.
   According to
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-document-providing-context-as-an-array-and-including-an-alias-for-an-additional-term.x"&gt;Example 3&lt;/a&gt;
   we have
   &lt;tt class="term"&gt;css&lt;/tt&gt;
   defined as a URI
   and then used directly as
   "&lt;tt class="term"&gt;css&lt;/tt&gt;"
   in the object.
   However in
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#fig-an-object-that-is-both-a-place-and-a-gr-location"&gt;Example 3&lt;/a&gt;
   the same syntax defines
   &lt;tt class="term"&gt;gr&lt;/tt&gt;
   as a namespace,
   and it then gets used as
   &lt;tt class="term"&gt;gr:catgegory&lt;/tt&gt;
   further down,
   rather than using just
   &lt;tt class="term"&gt;gr&lt;/tt&gt;.
   When is it a stand-alone attribute,
   and when is it a namespace-prefix?
   From the context it looks like something comes after the
   &lt;tt&gt;#&lt;/tt&gt;
   in the URI.
   But it doesn't appear to be explicitly documented
   from what I can tell.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Compact URI namespaces are a nightmare&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   While this is a JSON-LD thing,
   it make it particularly challenging
   to re-serialize an Object.
  &lt;/p&gt;
 &lt;/dd&gt;

&lt;/dl&gt;

&lt;p&gt;
 So an ActivityStream consists of
 maybe an action with an Actor
 and an optional object,
 or maybe it's just an object.
 And the
 &lt;tt class="term"&gt;id&lt;/tt&gt;
 might be present or it might not,
 and properties might be strings or objects or lists,
 and some might have external schema links
 with corresponding namespaces,
 or they use properties from those namespaces directly
 without a namespace prefix,
 and sometimes values consist of mappings
 from language-to-(possibly-unknown-)value,
 where those values might have some sort of undefined markup
 (that might or might not have unspecified
 security or privacy concerns),
 and to find the
 &lt;tt class="term"&gt;@context&lt;/tt&gt;,
 you have to look in multiple place.
&lt;/p&gt;

&lt;p&gt;
 Got it.
 That's ActivityStreams.
&lt;/p&gt;

&lt;p&gt;
 Except we should investigate the
 &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/"&gt;ActivityStreams vocabulary&lt;/a&gt;
 before we return to ActivityPub.
&lt;/p&gt;

&lt;h2 id="activitystreamsvocab2"&gt;ActivityStreams Vocabulary (part 2)&lt;/h2&gt;

&lt;h3 id="activitystreamsvocabgood"&gt;The Good&lt;/h3&gt;

&lt;p&gt;
 The vocabulary
 seem to cover
 a reasonable range
 of activities
 and objects,
 and the attributes mostly make sense.
&lt;/p&gt;

&lt;p&gt;
 We also get
 &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/#properties"&gt;a
 bit of clarification&lt;/a&gt; here.
 All those
 "it can be a String,
 or it can be an Object,
 or it could be a List"
 confusions in the ActivityStreams spec
 get clarified here.
 &lt;/p&gt;&lt;blockquote&gt;
  Properties marked as being "Functional" can have only one value.
  Items not marked as "Functional" can have multiple values.
 &lt;/blockquote&gt;
 If a "Functional" attribute
 has a String as the value
 and the type allows for a Link,
 it will be a URI,
 otherwise the String
 will be the particular value
 (such as the
 &lt;tt class="term"&gt;latitude&lt;/tt&gt;
 or
 &lt;tt class="term"&gt;radius&lt;/tt&gt;).
 If an attribute is
 &lt;strong&gt;not&lt;/strong&gt;
 labeled as "Functional",
 it can be a heterogeneous list
 of Links, Objects, and Strings
 (where String values are usually URIs).
 Mildly annoying
 to have to deal with all three possible value-types
 for most attributes.
 But at least this makes a bit more sense.


&lt;h3 id="activitystreamsvocabbad"&gt;The Bad&lt;/h3&gt;

&lt;p&gt;
 Okay, this is a bit of a disaster.
&lt;/p&gt;

&lt;dl&gt;

 &lt;dt&gt;Alphabetization&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   There are several large tables here
   consisting of object-types
   and properties.
   But the tables aren't sorted alphabetically.
   This makes it next to impossible
   to find things in my print-outs.
   Sure, they have internal HTML links
   but that's useless on paper.
   I get that they're grouped
   by similar functionality,
   but that doesn't make it any easier to find things
   because you have to know the groupings
   &lt;span lang="lat"&gt;a priori&lt;/span&gt;
   to know where to find them.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Same issues with ActivityStreams&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Again we see the
   &lt;a href="https://blog.thechases.com/posts/activitypub-activitystreams/hierarchy-inversion"&gt;hierarchy-inversion&lt;/a&gt;
   of
   &lt;tt class="term"&gt;Activity&lt;/tt&gt;
   and
   &lt;tt class="term"&gt;IntransitiveActivity&lt;/tt&gt;,
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Handling out-of-spec items&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The spec declares that a
   &lt;tt class="term"&gt;Question&lt;/tt&gt;
   may have a
   &lt;tt class="term"&gt;oneOf&lt;/tt&gt;
   or an
   &lt;tt class="term"&gt;anyOf&lt;/tt&gt;,
   but not both.
   However, if a malformed activity provides both,
   what is the correct response?
   Reject the activity?
   Choose one arbitrarily?
   There were a couple other places
   where such requirements 
   left open-ended the handling
   of errors and non-conformance.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Simple carelessness/inconsistency?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Sometimes the Vocabulary spec declares attributes as
   &lt;tt class="term"&gt;Object/Link&lt;/tt&gt;
   and other times it 
   &lt;tt class="term"&gt;Link/Object&lt;/tt&gt;.
   Does the order matter?
   Why are they different?
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Similarly, why are
  &lt;tt class="term"&gt;items&lt;/tt&gt;
  explicitly called out
  to accept a List?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   As cited above,
   if an attribute is not labeled as "Functional"
   (the 
   &lt;tt class="term"&gt;items&lt;/tt&gt;
   attribute isn't),
   it can take a List of values.
   Yet the spec for
   &lt;tt class="term"&gt;items&lt;/tt&gt;
   explicitly spells out that it can take a List of
   &lt;tt class="term"&gt;Object/Link&lt;/tt&gt;.
   What makes this special?
   Why not annotate that
   &lt;em&gt;every&lt;/em&gt;
   non-Functional attribute can take a List?
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Speaking of redundant attributes,
  how about the
  &lt;tt class="term"&gt;url&lt;/tt&gt;?
  &lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The spec defines a
   &lt;tt class="term"&gt;url&lt;/tt&gt;
   as accepting a Link
   &lt;strong&gt;or&lt;/strong&gt;
   an
   &lt;tt class="term"&gt;xsd:anyURI&lt;/tt&gt;.
   But based on everything I've seen in the spec,
   &lt;strong&gt;any&lt;/strong&gt;
   Link can just be a String containing the URI.
   Why the redundancy?
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Codify ambiguity&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Reading about the
   &lt;tt class="term"&gt;context&lt;/tt&gt;
   attribute,
   &lt;/p&gt;&lt;blockquote&gt;
    The notion of "context" used
    is intentionally vague.
    The intended function
    is to serve as a means of grouping
    objects and activities
    that share a common organizing context or purpose.
   &lt;/blockquote&gt;
   If your spec says something is
   &lt;q&gt;intentionally vague&lt;/q&gt;,
   the spec has issues.
  
 &lt;/dd&gt;

 &lt;dt&gt;Is HTML allowed in String values?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   A
   &lt;tt class="term"&gt;content&lt;/tt&gt;
   and
   &lt;tt class="term"&gt;summary&lt;/tt&gt;
   attributes state that the data defaults to HTML markup.
   However, the
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   can also contain text,
   but the spec explicitly disallows HTML.
   This feels irrationally inconsistent.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Some attributes take a "Map" suffix, others don't&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If you have a
   &lt;tt class="term"&gt;content&lt;/tt&gt;,
   &lt;tt class="term"&gt;name&lt;/tt&gt;,
   or
   &lt;tt class="term"&gt;summary&lt;/tt&gt;
   attribute,
   and you want to specify multiple languages,
   you use the
   &lt;tt class="term"&gt;contentMap&lt;/tt&gt;,
   &lt;tt class="term"&gt;nameMap&lt;/tt&gt;,
   and
   &lt;tt class="term"&gt;summaryMap&lt;/tt&gt;
   variants.
   It feels like this should have been used across the board.
   Use one attribute-name for a String value,
   a different name for an Object value,
   and yet a third different name for a a List value.
  &lt;/p&gt;

  &lt;p&gt;
   However, the ActivityStreams vocabulary specification
   &lt;strong&gt;doesn't detail these *Map&lt;/strong&gt;
   fields beyond a passing reference to
   &lt;a href="https://www.w3.org/TR/activitystreams-core/#naturalLanguageValues"&gt;Natural Language Values&lt;/a&gt;.
   The only reference to
   "Natural Language Values"
   in the ActivityStreams Vocabulary
   is the
   &lt;tt class="term"&gt;summary&lt;/tt&gt;/&lt;tt class="term"&gt;summaryMap&lt;/tt&gt;
   and nothing is mentioned
   regarding 
   &lt;tt class="term"&gt;name&lt;/tt&gt;/&lt;tt class="term"&gt;nameMap&lt;/tt&gt;
   or
   &lt;tt class="term"&gt;content&lt;/tt&gt;/&lt;tt class="term"&gt;contentMap&lt;/tt&gt;
   being "Natural Language Vocabulary".
   They only allude to 
   &lt;tt class="term"&gt;nameMap&lt;/tt&gt;
   and
   &lt;tt class="term"&gt;contentMap&lt;/tt&gt;
   without documenting why/how.
  &lt;/p&gt;

  &lt;p&gt;
   Additionally, can an object have
   &lt;strong&gt;both&lt;/strong&gt;
   the single version
   and the Map version?
   The spec only shows the exclusive cases,
   but the doesn't say whether an object can have both a
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   and a
   &lt;tt class="term"&gt;nameMap&lt;/tt&gt;
   attribute.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Start a new Relationship wit' you&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The
   &lt;tt class="term"&gt;Relationship&lt;/tt&gt;
   attribute accepts multiple
   (and semi-arbitrary)
   values,
   some from the
   &lt;a href="http://xmlns.com/foaf/spec/" title="Friend of a Friend"&gt;FoaF&lt;/a&gt;
   spec, and some from the
   &lt;a href="http://vocab.org/relationship/"&gt;Relationship&lt;/a&gt;
   spec
   (side rant: the
   &lt;a href="http://vocab.org"&gt;vocab.org&lt;/a&gt;
   site does a redirect and requires JavaScript enabled
   just to view the spec;
   A spec is a
   ████ &lt;!-- full-height block, censoring --&gt;
   text document).
   Are those the only values?
   What happens if they conflict or overlap?
   It's just kinda handwavey here.
   Yet more specs to read, I guess.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;In a Relationship? Says who?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The documentation for
   &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/#h-modeling-friend-requests"&gt;modeling a friend request&lt;/a&gt;
   references four different actors:
   &lt;/p&gt;&lt;ul&gt;
    &lt;li&gt;the &lt;tt class="term"&gt;actor&lt;/tt&gt; offering the friend-request&lt;/li&gt;
    &lt;li&gt;the &lt;tt class="term"&gt;target&lt;/tt&gt; receiving the friend-request&lt;/li&gt;
    &lt;li&gt;the &lt;tt class="term"&gt;object.subject&lt;/tt&gt; (the person seeking friendship)&lt;/li&gt;
    &lt;li&gt;the &lt;tt class="term"&gt;object.object&lt;/tt&gt; (the friend-to-be)&lt;/li&gt;
   &lt;/ul&gt;
   However, nothing in this example
   requires that the
   &lt;tt class="term"&gt;actor&lt;/tt&gt;
   &amp;amp;
   &lt;tt class="term"&gt;object.subject&lt;/tt&gt;
   be the same;
   nor does anything require that the
   &lt;tt class="term"&gt;target&lt;/tt&gt;
   &amp;amp;
   &lt;tt class="term"&gt;object.object&lt;/tt&gt;
   be the same.
   This means that Alice could send a request to Bob
   asking if Dave would accept Carol's relationship.
   Or Mallory could ask Bob
   if Bob would like to accept a relationship with Alice.
   This seems fraught with potential concerns
   that the spec leaves unaddressed.
  
 &lt;/dd&gt;

 &lt;dt&gt;Random &lt;tt&gt;acct:&lt;/tt&gt; prefix&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Continuing with the
   &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/#h-modeling-friend-requests"&gt;modeling a friend request&lt;/a&gt;,
   all four of those accounts use a
   &lt;tt class="term"&gt;acct:&lt;/tt&gt;
   prefix on a username.
   Why?
   Where is this defined?
   Not in the ActivityPub,
   ActivityStreams,
   ActivityStreams-Vocabulary,
   or the JSON-LD spec.
   Digging a bit,
   it looks like
   &lt;a href="https://www.rfc-editor.org/rfc/rfc7565.html"&gt;RFC-7565&lt;/a&gt;
   defines this
   &lt;tt class="term"&gt;acct:&lt;/tt&gt;
   but none of the ActivityPub-related specs
   reference this scheme.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Just like, Like and Unlike?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The vocabulary only defines a
   &lt;tt class="term"&gt;Like&lt;/tt&gt;/&lt;tt class="term"&gt;Dislike&lt;/tt&gt;
   activity.
   A post might elicit a whole range of reactions
   beyond like/dislike.
   It might make me laugh,
   or cry,
   or angry,
   or high-five,
   or any of a number of other emotions/emoji.
   As swell as it is to
   &lt;tt class="term"&gt;Like&lt;/tt&gt;/&lt;tt class="term"&gt;Dislike&lt;/tt&gt;
   things,
   it really needs a generic
   &lt;tt class="term"&gt;React&lt;/tt&gt;
   action.
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;I reject your answers and substitute my own&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   When responding to a
   &lt;tt class="term"&gt;Question&lt;/tt&gt;
   it's entirely possible to provide an
   &lt;tt class="term"&gt;inReplyTo&lt;/tt&gt;
   with a
   &lt;tt class="term"&gt;name&lt;/tt&gt;
   (answer to the survey)
   that doesn't correspond
   to any of the answers
   in the original 
   &lt;tt class="term"&gt;Question&lt;/tt&gt;.
   What should happen in this case?
   Reject the answer/reply?
   Add the answer/reply
   to the list
   of existing answers?
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;Survey-respondent privacy?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Looking at
   &lt;a href="https://www.w3.org/TR/activitystreams-vocabulary/#ex192-jsonld" )example 154&lt; a&gt;
   the
   &lt;tt class="term"&gt;result&lt;/tt&gt;
   of a
   &lt;tt class="term"&gt;Question&lt;/tt&gt;
   appears to return each vote
   &lt;em&gt;along with the ID of everyone who cast each vote&lt;/em&gt;
   leaked in the
   &lt;tt class="term"&gt;attributedTo&lt;/tt&gt;
   attribute.
   Is this expected?
   Is this required,
   lest folks want their answers kept private?
  &lt;/a&gt;&lt;/p&gt;
 &lt;/dd&gt;

&lt;/dl&gt;


&lt;h2 id="activitypub3"&gt;ActivityPub (part 3)&lt;/h2&gt;

&lt;p&gt;
 Okay, now we can finally return to ActivityPub.
&lt;/p&gt;

&lt;dl&gt;

 &lt;dt&gt;Naked Objects/Links&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   If you submit a naked Object/Link
   (one that doesn't have an activity associated with it)
   the server is supposed to
   automatically convert it into a
   &lt;tt class="term"&gt;Create&lt;/tt&gt;
   activity.
  &lt;/p&gt;

  &lt;p&gt;
   However, if the message
   has been cryptographically signed
   for authentication,
   changing from a naked Object
   to an Activity also changes
   the message's cryptographic signature.
  &lt;/p&gt;

  &lt;p&gt;
   Additionally, having two source fields
   (the
   &lt;tt class="term"&gt;Create.actor&lt;/tt&gt;
   and the
   &lt;tt class="term"&gt;Create.object.attributedTo&lt;/tt&gt;)
   leaves room for a bad actor to
   create objects with mis-attribution.
   E.g. Mallory publishes a Create action
   of Bob picking his nose,
   and attributes it to Alice,
   causing Bob to get mad at Alice
   rather than Mallory.
   The spec acknowledges this as a possible issue
   &lt;/p&gt;&lt;blockquote&gt;
    it should dereference the id
    both to ensure that it exists
    and is a valid object,
    and that it is not misrepresenting the object.
    (In
    &lt;a href="https://www.w3.org/TR/activitypub/#obj"&gt;this example
     [Example 7]&lt;/a&gt;,
    Mallory could be spoofing
    an object allegedly posted by Alice).
   &lt;/blockquote&gt;
   However, the spec doesn't detail
   &lt;strong&gt;how&lt;/strong&gt;
   to secure against this.
   Dereferencing the ID
   only checks that
   &lt;em&gt;something&lt;/em&gt;
   exists at that URI.
   Unless it's
   &lt;em&gt;exactly the same&lt;/em&gt;
   Object content
   (which can likely change
   since we're already munging with objects,
   normalizing things, etc)
   it means we can't likely do an exact-match comparison.
   Furthermore, because objects can be Links
   and Links can consist of a URI,
   that means that the linked object-ID
   could be some protocol
   &lt;a href="https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml"&gt;other
   than HTTP or HTTPS&lt;/a&gt;.
   Does my
   &lt;q&gt;dereference the id&lt;/q&gt;
   code need to support
   &lt;tt class="term"&gt;gopher:&lt;/tt&gt;,
   &lt;tt class="term"&gt;ftp:&lt;/tt&gt;,
   &lt;tt class="term"&gt;git:&lt;/tt&gt;,
   &lt;tt class="term"&gt;svn:&lt;/tt&gt;,
   &lt;tt class="term"&gt;imap:&lt;/tt&gt;,
   &lt;tt class="term"&gt;smtp:&lt;/tt&gt;,
   &lt;tt class="term"&gt;irc:&lt;/tt&gt;,
   &lt;tt class="term"&gt;ldap:&lt;/tt&gt;,
   &lt;tt class="term"&gt;smb:&lt;/tt&gt;,
   or whatever scheme?
   Maybe the object
   refers to other URIs/resources
   that exist,
   but aren't properly attributed.
   Determining if something is
   &lt;q&gt;misrepresenting the object&lt;/q&gt;
   seems handwavey.
  
 &lt;/dd&gt;

 &lt;dt&gt;Security/authentication/authorization&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   Standards, schmandards.
   &lt;/p&gt;&lt;blockquote&gt;
    ActivityPub uses authentication for two purposes;
    first, to authenticate clients to servers,
    and secondly in federated implementations
    to authenticate servers to each other.

    Unfortunately at the time of standardization,
    &lt;strong&gt;there are no strongly agreed upon mechanisms
    for authentication&lt;/strong&gt;.
   &lt;/blockquote&gt;
   (&lt;a href="https://www.w3.org/TR/activitypub/#authorization"&gt;source&lt;/a&gt;)
   Couldn't have made this
   &lt;a href="https://blog.thechases.com/posts/activitypub-activitystreams/#auth1"&gt;part of the initial requirements&lt;/a&gt;?
   Security is best left for an afterthought…
  
 &lt;/dd&gt;

 &lt;dt&gt;Serve it as what MIME-type now?&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
   The JSON-LD spec says to serve as
   &lt;tt class="term"&gt;application/json&lt;/tt&gt;
   while the ActivityStreams spec calls for
   &lt;tt class="term"&gt;application/activity+json&lt;/tt&gt;
   so just to be ornery,
   the ActivityPub spec mandates
   &lt;tt class="term"&gt;application/ld+json;profile="https://www.w3.org/ns/activitystreams"&lt;/tt&gt;
   as the MIME-type.
   Sure, you
   &lt;em&gt;can&lt;/em&gt;
   accept other types,
   but why not make the spec
   consistent to begin with?
  &lt;/p&gt;
 &lt;/dd&gt;


 &lt;dt&gt;&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
  &lt;/p&gt;
 &lt;/dd&gt;

 &lt;dt&gt;&lt;/dt&gt;
 &lt;dd&gt;
  &lt;p&gt;
  &lt;/p&gt;
 &lt;/dd&gt;

&lt;/dl&gt;

&lt;h2 id="activitypub4"&gt;ActivityPub (part 4)&lt;/h2&gt;

&lt;p&gt;
 So how do you go about starting the whole process?
 You use
 &lt;a href="https://datatracker.ietf.org/doc/html/rfc7033"&gt;WebFinger&lt;/a&gt;
 (which the ActivityPub,
 ActivityStreams,
 ActivityStreams Vocabulary,
 and JSON-LD specs don't mention).
 You start by hitting a
 &lt;a href="https://datatracker.ietf.org/doc/html/rfc5785"&gt;well-known&lt;/a&gt;
 URL like
 &lt;kbd&gt;https://example.com/.well-known/webfinger?resource=acct%3A&lt;var&gt;username%40example.com&lt;/var&gt;&lt;/kbd&gt;.
 This should return an initial JSON object
 describing the person,
 including their
 &lt;tt class="term"&gt;name&lt;/tt&gt;,
 &lt;tt class="term"&gt;id&lt;/tt&gt; (URI),
 &lt;tt class="term"&gt;Inbox&lt;/tt&gt;,
 &lt;tt class="term"&gt;Outbox&lt;/tt&gt;,
 cryptographic public keys,
 as well as various other properties.
 And for what it's worth,
 because we haven't already
 had enough MIME-types
 for ActivityPub data from other specs,
 WebFinger returns as
 &lt;tt class="term"&gt;application/jrd+json&lt;/tt&gt;,
 yet one more.
&lt;/p&gt;

&lt;p&gt;
 Additionally, because the well-known WebFinger URL
 &lt;strong&gt;must&lt;/strong&gt;
 be rooted at
 &lt;tt class="term"&gt;/.well-known/webfinger&lt;/tt&gt;,
 it prevents the ActivityPub server
 from being rooted in some subdirectory.
 So either the ActivityPub server needs to largely control the web-root;
 or it needs two distinct processes,
 one listening for the WebFinger request,
 and another one rooted off a name-spacing sub-resource.
&lt;/p&gt;

&lt;h2 id="auth1"&gt;Authentication, authorization, and signing&lt;/h2&gt;
&lt;p&gt;
 There's some discussion of
 &lt;a href="https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization"&gt;Authentication and Authorization&lt;/a&gt;
 but only descriptive aspects in the context of
 "this is what Mastodon does,"
 not a prescriptive
 "this is how everybody should do it."
&lt;/p&gt;
&lt;strong&gt;TODO: WIP&lt;/strong&gt;</description><category>activitypub</category><category>activitystreams</category><category>jsonld</category><category>rant</category><guid>https://blog.thechases.com/posts/activitypub-activitystreams/</guid><pubDate>Thu, 01 Feb 2024 19:35:08 GMT</pubDate></item><item><title>Planning the day on the CLI with tsort</title><link>https://blog.thechases.com/posts/planning-the-day-on-the-cli-with-tsort/</link><dc:creator>Tim Chase</dc:creator><description>&lt;p&gt;
I had a bunch of items on the todo list for the day
and wanted to arrange them in order,
but I needed to certain items before others:

 &lt;/p&gt;&lt;ul&gt;
  &lt;li&gt;
   Drop donations off at the resale store,
   but can't do that before 10:00 when they open
  &lt;/li&gt;
  &lt;li&gt;
   Get the daughter ready for soccer camp before dropping her off
  &lt;/li&gt;
  &lt;li&gt;
   Drop the daughter off at camp
   before taking paperwork up to her school
  &lt;/li&gt;
  &lt;li&gt;
   cut the son's hair
   before he
   takes a shower
   and
   cleans his bathroom
  &lt;/li&gt;
  &lt;li&gt;
   pick up daughter at camp
   after she's started camp (duh)
   and before 10:45
  &lt;/li&gt;
  &lt;li&gt;
   take the kids to the library
   after picking up daughter at camp
  &lt;/li&gt;
  &lt;li&gt;
   for obvious reasons,
   8:30 → 8:45 → 9:00 → 10:00 → 10:30 → 10:45
  &lt;/li&gt;
 &lt;/ul&gt;

So I expressed this in a file
where each row contains
"X needs to come before Y":

&lt;pre&gt;
&lt;label&gt;$ &lt;/label&gt;cat todo.txt
10:00 resale
resale 10:30
sunblock take_to_camp
pack_snack take_to_camp
pack_water take_to_camp
pack_towel take_to_camp
take_to_camp 8:30
8:30 soccer_camp
soccer_camp 8:45 
soccer_camp paperwork
8:45 home
home 9:00
paperwork home
home cut_hair
home clean_bathroom
home shower
cut_hair shower
cut_hair pick_up
shower pick_up
clean_bathroom pick_up
cut_hair clean_bathroom
clean_bathroom 10:00
10:45 pick_up
8:30 8:45 
8:45 9:00 
9:00 10:00 
10:00 10:30 
10:30 10:45 
pick_up library
&lt;/pre&gt;


&lt;p&gt;
Now it just became a matter of passing these requirements to
&lt;code&gt;tsort&lt;/code&gt;
&lt;/p&gt;&lt;pre&gt;
&lt;label&gt;$ &lt;/label&gt;tsort todo.txt
pack_towel
pack_water
pack_snack
sunblock
take_to_camp
8:30
soccer_camp
paperwork
8:45
home
cut_hair
9:00
shower
clean_bathroom
10:00
resale
10:30
10:45
pick_up
library
&lt;/pre&gt;
to sift them into the required order
of my todo list for the day.</description><category>tsort</category><guid>https://blog.thechases.com/posts/planning-the-day-on-the-cli-with-tsort/</guid><pubDate>Mon, 31 Jul 2023 12:48:08 GMT</pubDate></item></channel></rss>