December Adventure 2024
Entries
- Sun, 01 Cerca
- Mon, 02 Cerca
- Tue, 03 Cerca
- Wed, 04 Cerca
- Thu, 05 Cerca/umount
- Fri, 06 dd + pv
- Sat, 07 Cerca
- Sun, 08 pad software
- Mon, 09 pad software
- Tue, 10 pad software
- Wed, 11 pad software
- Thu, 12 pad software (crdt links!)
- Fri, 13 pad software
- Sat, 14 CRDT/OT deep dive
- Sun, 15 CRDT/OT deep dive
- Mon, 16 Cerca
- Tue, 17 Cerca
- Wed, 18 Rad Reader
- Thu, 19 noop
- Fri, 20 scripting
- Sat, 21 noop
- Sun, 22 (new!)
2024-12-01
Time to adventure.
We’ll see how this month meanders across projects. Top of mind right now is Cerca’s invites function (PR). I started to build it out while visiting friends in Trento last week:
Cerca is a forum software I created after a long time of missing that type of slow cooking long-form interaction, and feeling disappointed with the present state of solutions. It was originally created to host the Merveilles community forums, but has now grown into its own project.
When the forum was started, registration was limited to already existing members of a community. Accounts could be registered by validating a verification code received from the registration form.
How it worked: Each visit to the registration page would show a newly generated verification code. The idea was that anyone part of the community could take the code presented to them and put it on a domain allowlisted as being part of the community, and tying a bow on the process by including a link to where the verification code was hosted, back in the register form; essentially proving you were part of the community. The forum software would validate that the specific code had been generated by the forum, and that it was hosted on an allowed domain, and if both checks passed you would now have an account!
This was great for bootstrapping the forum with people from the community. One avenue used to populate the domain allowlist was our Mastodon instance, and the other was the set of sites part of the community webring. Over time, this way of posting codes on Mastodon broke because its devs decided stop statically generating pages and to instead to require Javascript to render content, making it difficult to agnostically scrape (i.e. not use Masto APIs) a post with a verification code. However by this time, most of the people from the webring that wanted an account had one. The idea to remove the now-obsolete routine started to percolate.
Account registration then went into a phase of having admins create accounts for people as-needed. First it was me doing it with a cli tool directly on the server, then later using an administrative panel hosted in the forum which I cooked up last December Adventure. This works quite well actually. But recently invites came up again, and I finally had a good idea of how to solve it in a way compatible with the forum software’s internal logic and values.
Today’s Side Quests
oh shit i discovered by posting this that i need to fix my rss generation for the new nested posts functionality of my ssg <.<
- Fix nesting under /posts/ wrt rss
- Consider some way of not creating yet another subfeed when just wanting to add rss items to another of
the feeds. e.g. use
cc
with an empty / shibboleth operand to say “just append to<x>
feed”
Fixed the <hr>
styling of the ssg and created a cosy lil theme for this page :]
- Implemented a first pass of a routine that returns all unclaimed invites by batch (commit)
- Remembered that i have to be careful when manipulating contents retrieved from a map, and to sprinkle around references to make sure values are recorded as i expect them to (commit)
- Think that’s enough coding for today! Now to switch to baking vegan lussebullar. In an attempt to make them a bit healthier, i’ll switch the usual margerine / butter for oil—following the rule of thumb: oil amount in grams = 80% butter amount in gram—and use half and half sugar / date sugar!
2024-12-02
Today, in December Adventure
The day’s by far largest priority is… to clean! Not anything code, just my apartment. It’s in a state of semi-chaos after traveling and having arrived back home with a cold that lasted most of last week. Yesterday’s lussebullar never happened ‘cause I ran out of steam. The revised plan is to bake a half batch during the week, and a restored kitchen will be a big boon to that end!
On the code-front it’s time to wire up my database routines for the invite functionality to something that can actually call them. I’ll be implementing handlers for the different HTTP calls that will be originating from the invitation panel and the register form. I’ll start with the invites panel and its generate button, and then see where things start to break.
While reading Lee’s decadv log, I started to wonder if I should shoot him a build of the new static site gen tool I’ve been working on. Maybe not, he seems to be having fun creating a new Lee-shaped generator. But, maybe the input he’s created for his generator could be another useful testbed for my tool. To see where its functionality is lacking, what can be improved. I’ll ask him for the repo when my december adventuring swings away from forum stuff and back into tinkering in site gen land!
Dreary day, weather-wise. Like the dregs of a coffee cup, or a foggy day without any of the mystery. Brewed the last leaves of my Henta Asanoka sencha. *sigh* I guess it’s time to tidy. Currently listening to a set by Maï-Linh on NTS from earlier this year; excellent mix of trance and other tracks.
Skimmed HTMHell’s 2024 advent calendar entry for
today. It was about
autofocus
,
which focuses an element on page load. Decided to sprinkle some around the forum; one for the title <input>
when
creating a new thread, another for the <textarea>
in the edit post view
(commit).
Recorded a stray Idea (TM, etc) I might prototype later in the month.
Noticed I was missing superscript styling on the blog, snuck some in:
sup {
position: relative;
top: -0.5rem;
font-size: 0.5rem;
}
Ahhh, finally, the ritual of tidy is complete. I am free once more!!! While not a member of the fanclub of recreational tidying, I do enjoy its effects, such as having an empty sink.
After a bowl of yoghurt and a cup of rosemary tea it’s time to start today’s Cerca session in earnest!
We can now press a button to generate a batch of invites and display them without any mocked up HTML! (commit)
During implementation, I realized I was being a bit verbose when creating descriptive errors:
// i was doing this
err := errors.New(fmt.Sprintf("nice and descriptive text for this error i'm preparing to throw"))
// but i changed it to this v------------v
err := fmt.Errorf("nice and descriptive text for this error i'm preparing to throw")
THE DIFFERENCE!
Done for today!
For tomorrow, maybe I’ll see about revamping the register view and update the parts dealing with the old approach to use invite codes instead. It’ll let me delete a lot of lines that aren’t going to be useful anymore :)
2024-12-03
Previously, on December Adventure
We have the basics of invite creation and deletion working. All the scaffolding is in place for the actually important part: being able to register an account by claiming an invite! That’s what I’ll start to dig into now. I bet it will be a bit gnarly, that there will be unexpected changes and considerations to take into account. Exciting! I’m eager to remove vestigial parts of the forum’s structure, and instead introduce something that will be useful going forward.
This morning’s tea is an ecologically certified Longjing aka Dragon’s Well. It is a Chinese-style of tea, where leaves are usually panfried and for this particular tea it is smooshed in a just-so characteristic way. I was rummaging around my cupboard and inspected a large packet and re-discovered I’d bought a respectable amount during a previous foray at the local tea shop. Less kick compared to sencha, and an always pleasing taste.
One chunky change later, and the forum is now able to register accounts using invite codes! This change removes so much incidental complexity in the codebase, i love it. (commit)
I think this feature is fundamentally done now??? I will take tomorrow to look through the
changes done so far and see where I can spot any clean up opportunities. One clear point of
improvement has to do with the invites panel, html/admin-invite.html
, where I need to create
better register links.
Once that’s sorted the adventure will detour away from code and into the realm of wrangling ones and zeroes in another manner…
2024-12-04
Deceeeeeeeeeeeeeeeeeember Adventure!
Spruced up the edges of the invites functionality by sprinkling descriptive text around the invites
panel and re-used the root URL from the RSS functionality to output nicer-looking registration
links. We now also log acts of invite creation / deletion to Cerca’s moderation log for some
added transparency and history.
(commit)
Next up: considering d1’s suggestion of repeat-redeemable invites. On a technical level, it would basically entail extending the invites table with a bool column that determines if an invite should be deleted on being claimed or not. On the social level, I’m wary of repeat invites as a spam vector. It would be a useful feature though; for instance, a single invite could be posted in a local-only Hometown post and linked to from the registration form’s instructions. This would be a much smoother experience for pretty much everyone involved compared to continual management of invite code batches, at least for spaces where community members already have access and are eligible as forum members.
Before that, though: lunch. Might fry up yesterday’s rice with some garlic and onion, add a few cubes of tofu and serve with sprigs of kale on the side.
2024-12-05
In which the first part of the adventure nears its conclusion…
Sleeeeeeepy morningggg~
Still mulling about d1’s repeat-redeemable invite idea, but I think I’ll just go ahead and implement it. Probably add a new category to the config for feature flags, and default to having repeat-redeemable invites as off. This new config section could be re-used by the work luke proposed to add for making every flag able to be set from the config as well.
Found another sencha in the back of my cupboard, I think it’s the last of my Fukamushi Murasaki sencha. Fukamushi meaning, like, deep steamed? Which is apparently a style of tea that is very popular for the Tokyo region. Japanese tea connoiseurs are less interested in this style of tea as they all taste the same, there’s no apparent regional notes, but I think I like them because the base taste level is just really high.
Badabing badaboom: reusable invites (commit).
And right on time: I received a notification to go pick up the first hard drive I have bought in… 8 years?
I haven’t had a functional external storage drive in at least that long, maybe longer. The need arose and it was a limit to getting some—I regret saying this—well-needed sysadmin over with, so I decided to buy an external SSD. While hefty in terms of bytes (I’ve burned myself in the past by buying external storage that was juuust too small), its physical dimensions are absurdly small:
Currently giving it a test by copying over a folder of photos, and after that I want to connect it to my tiny Thinkcentre server to see if the default exFAT format works out-of-the-box.
It’s not until tomorrow, at the earliest, that I’ll get into the drive’s use case proper.
Put together a little guide on mounting & unmounting USB drives in Linux. Originally just wrote it in my wiki, but I dropped on the website just in case its of use to anyone else.
2024-12-06
Heading over to a friend’s place to code today! Had a spur of the moment idea yesterday I might purse while it’s still hot. If not that then I’ll sketch up the feature flag for the reusable invites, if only for the practice. Feeling myself soften to the notion of having it in without locking it behind any flag at all.
Looking into dd
for making an image of a disk and mentioned it to my friend, asking whether I
could get it to display a progress bar and found out that there’s something called pv
(pipe
viewer) that is like dd
but with better defaults. It has none of the catastrophically
error-prone if=
and of=
syntax, and displays progress as part of the default output.
Haven’t run it yet but usage looks very straightforward:
pv input > output
2024-12-07
Good morning, happy saturday etc- today will be mostly a food day, but I’m squeezing some computer in here at the very onset! I’ll attempt yesterday’s initial plan, and sketch out some feature flag support just for the practice :]
Currently listening to one of Pam’s shows over on NTS. This one starts bombastically with classical music, and what a lovely way to start the day. Really like these hosts and shows of the Early Bird segments NTS does, great selections and a very modest amount of talking.
A few minutes later and we have it in: (commit)
It’s in a branch separate to the invites while I mull over the necessity of its inclusion :)
Observations:
- Putting features behind a feature flag works out of the box if you default the feature as off. This because already existing configs lack the section for the newly added flag.
- I still haven’t seen appealing solutions on how to deal with updating or migrating already-written configs to reflect upstream changes of the config format. Feels gnarly.
- Thanks to Cerca’s server module and its consistent use of a struct with a reference to the config’s values, this was very painless to thread through to all the relevant places.
That was a quick little adventure indeed! I’m not even one third through my little morning cup of tea (Maojian, today, in case you were wondering - a super solid work-horse of a tea in my opinion, can drink it any amount of days in a row and not tire).
Aside: I love the feeling I get when I start the day pretty much immediately with a chug of water, a warm cup of tea, and then straight into exploring some project on my work-focused distro while listening to something pleasing. It gives me a lot of immediate satisfaction and a physical sensation of well-being and focus and creates such a stellar start to the day.
At times I slip away from this ideal, because of poor habits reappearing. So I try to make note of these moments and how gratifying and self-fulfilling they are in the moment.
2024-12-08
Slow Sunday vibes today. Not a lot on the schedule except sorting out lunch, which is looking to be a stir-fry with tofu, oyster and shitake mushrooms.
Will see what I get up to in coding terms, but at the moment I feel like seeing how far I can get with knocking together a proof of concept for the idea I had on Friday.
The proof of concept is up and running: it’s a tiny pad software! Haven’t decided entirely where to go with it, but I wanted to see how much work it would be to get something very small working. Pad creation, viewing, saving, and deleting works; code forthcoming in the next few days.
That marks one week of december adventuring! I’ve enjoyed participating so far; whether from the boost of focus towards tending my own projects or from following along in the adventure logs others have been posting. The pace I’ve set so far has been very manageable, some times boosting ahead on a wave of momentum and other days getting a few minutes, lines, and words in and calling it a day.
Onward~~
2024-12-09
Might continue tinkering with the pad software today and explore progressive enhancement of its baseline functionality.
After reading docs and even glancing through a W3C spec (!) I now have a modest amount of javascript handling the majority of input cases. The code even takes care of the case of deleting a current selection as a result of another input event happening (so selecting some text and then pasting in something, or selecting some text and pressing ‘a’).
Docs:
- InputEvent
- InputEvent: inputType property
- W3C spec! “Input Events Level 2”
- HTMLInputElement: select event
This was a fun, exploratory change of pace from how I usually tackle problems :)
2024-12-10
We’re in double-digits territory of the december adventure! I’m continuing my tinkering with the pad software, and today is focused on preparing to communicate pad changes. The goal is to do it over plain ol’ websockets (never worked with ‘em in golang before, i think!). But before starting on that proper, which entails a lot of book-keeping to handle edgecases of trimming disconnections etc I figured I would use the BroadcastChannel interface instead and limit myself to only sending strings across it, which is what I’ll do later with the proper websockets connections.
BroadcastChannel is a little browser API that allows tabs and windows for the same domain (same page?) to communicate with each other using:
<channel>.postMessage(<data>)
on the sending side, and<channel>.addEventListener("message", <eventHandler>)
and the receiving end.
This will let me off the hook from having to do the websockets busy work while I get the actually important logic (updating the pad based on remote events) working as I want it to.
When I sat down I only had 15 minutes before I needed to take care of a chore (laundry timer!) and I already have a queue of events being sent across tabs! :) The 15 minutes also included writing this!
Wanted to put up the code in a community git hosting service that I’ve been woefully under-utilizing—and ran into some git bureaucracy! Below are the commands and instructions I cobbled together to be able to push to an ssh remote for my repository using a keypair generated specifically for that service.
On the service-side, in its web interface, I provided my generated ssh public key. The public key is output by the last command, below.
# generate a key. in this example, i am naming it as `service-keys.ed25519`
ssh-keygen -t ed25519 -a 100
# move the public and private key from the current working directory (where it is put happens if you only specify a new name and don't go with the default)
# the first one is the private key, and the second is the public key
mv service-keys.ed25519 service-keys.ed25519.pub ~/.ssh/
# while standing in your repository, run this command to tell that repo to use this keypair when pushing over ssh
git config --local core.sshCommand "/usr/bin/ssh -i /home/me/.ssh/service-keys.ed25519"
# now, on the external service, in the section where you 'add a ssh key': give that dialog the output from the command below
# i.e. the public key
cat ~/.ssh/service-keys.ed25519.pub
Oh, and I’ve got the edits syncing and changing the pad’s textual contents across tabs now too :~~~~
2024-12-11
Continuing to work on the pad software, I realized I’ve tricked myself into working on what basically amounts to a text editor; not that I mind, it’s been both enjoyable and soothing!
By looking at how my browser deletes words when you do e.g. ctrl-backspace or ctrl-delete, I
think I’ve managed to reverse engineer the behaviour. I use a combination of word boundaries
\b
, checking if a given match contains any alphanumeric symbols, and a bit of extra heuristics to
properly delete punctuation characters, \p{P}
.
At the very least, the samples I was trying against seem to act correctly when deleting whole words and syncing the changes. It has been a gratifying process and made possible entirely by decadv’s chill vibes.
2024-12-12
It’s misty outside, today.
Looped back to the forum this morning and implemented a great suggestion from d1 to add a tally of invite uses, so as to allow admins to monitor any reusable invites going amok as well as better visualize which invite batches are being used. (commit)
On the pad side of things, I added support for handling whole line deletions via ctrl-shift-backspace and ctrl-shift-delete. Also started to sketch how I could handle undo/redo events.
Yesterday, while thinking more about the pad software, I realized I may be in for an exciting set of conflicts when mutiple users are typing at the same time when one user’s change event is based on a past that doesn’t reflect the current state of the user receiving the remote event. Of course, this is where CRDTs excel, but I want to see how far I can go without having to include any ready-made algorithm! CRDTs also have a difficulty in deciding when and where to trim the history of edits, if at all, which I’d love to not have to think about for this little piece of software.
Since there’s a server component I was thinking of going with the flow and have it make any necessary modifications to incoming events before passing them on; acting as the source of truth. Before that, though I need to be able to simulate these conflicts. I’ll see about making small scripts to cover the different situations e.g. editing text above the active user’s caret, and editing text below it + introducing delays. I guess I’m basically luring myself into learning about operational transforms.
Looking forward to after lunch, when I’ll brew some newly received kukicha green tea. It’s tea made out of the twigs from the tea bush, as opposed to the top leaves. There’s a pleasingly sweet flavor about this kind of twig tea that I really enjoy:)
On the topic of operational transforms and CRDTs, some reading:
- https://github.com/cryptpad/chainpad
- https://en.wikipedia.org/wiki/Operational_transformation
- https://databases.systems/posts/collaborative-editor
- https://quilljs.com/docs/delta/
- https://quilljs.com/docs/guides/designing-the-delta-format
- https://docs.yjs.dev/api/delta-format
- https://docs.etherpad.org/easysync/easysync-full-description.pdf
- https://josephg.com/blog/crdts-go-brrr/
- DSON, a space efficient δ-based CRDT approach for distributed JSON document stores
- https://www.figma.com/blog/how-figmas-multiplayer-technology-works/
- https://github.com/pfrazee/crdt_notes/tree/68c5fe81ade109446a9f4c24e03290ec5493031f
- https://mattweidner.com/2024/06/04/server-architectures.html
- https://mattweidner.com/2022/10/21/basic-list-crdt.html
- https://prosemirror.net/docs/guide/#collab
- https://marijnhaverbeke.nl/blog/collaborative-editing.html
Had a fun idea on my walk while thinking about OTs, think I’ll attempt an implementation and see how it bears out in practice! I did it: blocking edits by relying on social norms, see video.
2024-12-13
Bit of a miscellany day today; a slower tempo. Worked on the pad software and added a scratchpad that doesn’t sync. Other than being a private drafting area it also has the function of absorbing local edits that happen when someone else has the word.
In other areas of life: it’s ramen night! I have two jars going for a vegan dashi broth, the one soaking up kombu flavors and the other extracting shiitake goodness.
While out for a walk earlier I bought a hybrid cinnamon bun / lussebulle on this day of lucia from my favorite neighbourhood bakery to split for post-ramen dessert :) I was going to bake but the electricity prices are peaking due to questionable cross-country energy market decisions affecting the entire region. So I postponed the baking for tomorrow when prices are expected to plummet. Baking geopolitics.
2024-12-14
Slow saturday vibes. Spent some time reading a few of the CRDT links I gathered two days ago (see above) and sketching / exploring the basics of implementing an operation-based plaintext-focused naive approach and seeing what I can learn.
For CRDTs there seems to be a few different main tricks. One is to give every character a name that doesn’t change, and to anchor each change to the character that comes before it—referencing that character by its immutable name. Doing things this way circumvents the need to transform indices across different authors. This name is sometimes constructed in a way that is reminiscent of how an SSB append-only log is constructed: each author has a randomly generated ID, and each operation can be viewed as a new log entry and identified by its monotonically-increasing sequence number.
It seems many solutions cause a grow-only behaviour of the document, and any changes that
delete a character effectively hide it instead. Either the character is explicitly set as
visible: false
or it is shuffled from the document set and into a tombstone set to mark it as
deleted (aka two-phase set). This tombstone set is then a grow-only set.
2024-12-15
Continuing to explore a naive OT sketch I started last night.
I reckoned that after reading many of my gathered links and checking out various implementations then the next reasonable step was to continue getting my hands dirty and to implement a modest amount of functionality at a time, and to test it out against best case situations. Yesterday I implemented an insertion op and created a naive transformation function to adjust the indices of the one operation with the changes caused by the other. Today I extended that code slightly with the introduction of a removal op. Now, however, it’s pancake time.
2024-12-16
Just a small thing today with a big impact: merged in the invites functionality for Cerca:
https://github.com/cblgh/cerca/pull/92
2024-12-17
Fixed a bug in Cerca that snuck in along the recent batch of work and added a signifier on the thread index view so that people have an indication of how threads are being sorted: by most recent post, or by most recent thread.
Sorting by most recent thread allows new threads not to get forgotten too quickly, and sorting by most recent posts lets people see when older threads are receiving new activity and being bumped.
Luke suggested both the signifier and spotted the bug, very glad to have extra eyes and hands helping out! :>
Wrote a little celebratory post as I reflected on the past year of Cerca collabs and improvements: https://merveilles.town/@cblgh/113668798687920387
2024-12-18
I decided to use today’s adventure to assemble words for an update on Rad Reader, my rss reader! I’ll hold off on publishing the update for now. Both to let it stew a bit, but also to give myself a chance to work on and put out a new release with some fixes (incl. a new release to support the latest version of Ubuntu!).
However I won’t be releasing any updates before 2025 is here ‘cause I don’t want to have to even consider rushing off any hotfixes during this busy and otherwise stressful period of the year :)
Continued the writing track to brush up on a new CV as I prepare to apply for some jobs in 2025 (yes, I’m looking for work or consulting contracts! Get in touch.)
2024-12-19
Travel day, which meant that december adventuring was (for the purpose of this log anyway) a nop!
2024-12-20
Holiday slow mode has been engaged. Visited my grandmother, she’s 80 something and lost most of her sight over the past years. Always nice to swing by and catch up, hear some stories from the past and talk a bit about the present :)
Spruced up a script that produces styled PDFs from markdown documents using pandoc.
2024-12-21
December adventure noop yesterday but I made italian cookies (baci di dama; ‘lady’s kisses’) and ratatouille :)
The ratatouille had an interesting herb oil base. It was made by crushing sage, garlic, chili and lemon zest with salt using a mortar & pestle and topped off with 2 tablespoons of oil.
2024-12-22
Now that I have a baseline setup in place, maybe that’ll let me get some computer time in :) I’m sitting on a footstool with my laptop in front of me on an old low wooden living room table I remember us dragging home in the snow on foot 20 years ago.
Not sure what I’d like to continue with, project-wise. Getting a websockets server up and running for the pad software would make sense since I have that project on this computer. I’d also like to get some writing time in so that I can finish off a couple cover letters for applications I’d like to send off in the next days.
On the reading front, I’d love to sink my teeth into Awk, first edition and continue reading Dawn of Everything by Graeber and Wengrow.
When researching which Websocket lib to use I came across an article, written by the author of one of the modules I was considering (!) with a rationale regarding the other module on my list (!!) - perfect!
The concept for https://nhooyr.io/websocket came to me when I was working at Coder in 2018 on their collaborative cloud editor. I had implemented an operational transformation websocket protocol in Go with gorilla/websocket.
It worked but I was not a fan of gorilla’s API surface nor the callback approach for receiving pongs. I had much spare time back then and those two minor issues gave me the impetus to build a library suited to my minimal tastes.