Welcome

This website is the personal knowledge base of @jakintosh. It is a collection of thinking, writing, work, and reflection. The goal is to communicate both knowledge and process, and so every page is composed of both a garden and a stream.

The Garden is the manicured, pruned, and always evolving "living document" for a given topic or project. The garden captures the current state of my knowledge, and is the primary content at the top of each page.

The Stream contains a reverse chronological action log that shows what has happened to get the garden to its current state. The stream captures the historical process of knowledge on the site, and can be found at the bottom of every page.

Highlighted /pages

  • /coalescence landing page for my philosophical work
  • /degrowth a movement towards a new way of living
  • /collapse an understanding of the social reality we occupy
  • /repair a practice and worldview centered on agency and understanding
  • /coalescent computer my project for humane networked computing

Recently updated /pages

Recent /stream updates

March 25, 2025

/stream /programming

Over the past few decades, as programming has moved from assembly language to higher-level languages like C, from desktop to cloud, from raw text editors to IDEs to AI assisted coding where sometimes one barely even looks at the generated code (which some coders recently started to call vibe coding), it is getting easier with each step.

Andrew Ng

Software engineering has an inherent minimum complexity. The gap between its minimum complexity and the effort needed to do it is the surface area for unintended consequences. Historically, this is where "abstraction" comes into play. Well considered and thoroughly tested abstractions tend to balance these tradeoffs well, both through intention and natural selection.

This is why C is still alive and well, after so many years and countless "C-replacements": it trades off the right parts of minimum complexity for acceptable surface area of risk. On the other hand, we have the framework hell of modern web development. This approach to abstraction gets it exactly opposite: this philosophy tries to trade off (read: obfuscate) parts of the discipline that don't need to be removed, in exchange for unacceptable risk. For all the "dangers" of C, which category of software do you find is bloated, non-performant, and riddled with bugs: C programs, or modern web-apps?

LLM-assisted vibe coding is not an extension of the "C family" of abstraction. It is firmly in the camp of obfuscation, but pushed an order of magnitude further than framework hell ever has. The only possible outcome here is that a lot more software gets created that has more problems, and is significnatly less performant. Given the precarity of the modern software stack, vibe coding may finally push the house of cards to the limit.

And then we'll be left with the people who know how to write C.

January 3, 2025

/stream /linux

Last night, I went to write a quick blog post before bed, but when booting my laptop it was hanging just before reaching the login screen. I rebooted with debug logging parameters for systemd, and saw that the last message was [OK] Reached Target Graphical Interface. I hadn't run any pacman updates in my previous boot, so I couldn't imagine why my graphics would be crashing. I don't actually feel like relaying the play by play of the debugging process, only the lesson, but I add this first piece for search discoverability. After two hours of pulling on threads (so much for getting to bed at a reasonable hour), here's what happened:

A few days earlier I changed a local deployment related shell script that I was using for a project I'm working on from using cp -r commands to copy resources to an rsync -a --del command. One of the sets of resources I was deploying were systemd service units, being deployed to... /etc/systemd/system/. The --del in the rsync command deletes any files that are not part of the sync, which was useful for the html templates that I was deploying to a dedicated directory, but was decidedly not helpful when the target destination was my system service unit directory.

Essentially, the change to the script nuked my systemd's system units, which broke the getty@tty1.service unit which is responsible for displaying the login prompt after the boot succeeds. I didn't know this service existed, and only managed to discover this after noticing that tty2 and friends were functional. Once I got in, saw some other broken systemd pieces, and traced the error, I copied the homework of enabled services from a functioning installation.

This experience has me thinking two things: one, that I've proven the value of something like Docker, which I was explicitly avoiding in order to minimize the overhead and complexity of deploying server programs; and two, that I've learned a valuable lesson that makes my continued use of systemd as an "orchestration tool" less dangerous. Or maybe the lesson was "think three times before using the --del flag on rsync".

December 31, 2024

/stream /december-adventure

In the final few days of this adventure in the week between Christmas Day and the new year, I had a lot of fun sprinting to get consent across the finish line in some kind of "milestone" way.

The first thing I did was create a way to register "services" with the system, which are just text JSON files that the admin puts in a folder, and passes the folder path as an environment variable. I figure that the system already assumes an admin who can deploy a systemd service on a linux machine, and so editing some text files over ssh or rsync should be a reasonable expectation. It also keeps the complexity of the program super low: just read all the files in the service directory. Since it's JSON, I (or someone else) could easily make this more complicated later if they had a good enough reason to do so.

The second thing I did followed these service definitions, which is that I generalized the "file watcher" functionality that I wrote to dynamically load templates, and then applied that to these service files too. This means that, while the program is running, an admin can hot-reload the html templates for the UI and also the service definitions, and the program can keep going without downtime or redeployment. Very rsync-friendly. If you're interested in the watcher code (which uses fsnotify), it's pretty succinct and you can read it here.

After services were working—which, as I mentioned in my last update, was to improve security to prevent bad actors from hijacking redirect urls—I wanted to add CSRF protection by giving each refresh token a secret code that I could double submit with any destructive API calls. Essentially: the refresh token has a secret code in it, and the server also populates that code in the page itself as parameters to something like a <form> POST. Assuming that TLS is securing the page, and the browser is not compromised (so the tokens are safe), then only a person who is on the page and has the tokens will be able to match both codes; the server statelessly checks the match, and a request forgery would be caught.

Anyhow! This meant that I wanted non-standard data in my JWTs, and the library I was using was super annoying about doing that. And so: * 637247e - changed: wrote my own JWT implementation for some reason (2024-12-29 11:00PM). This was actually a lot of fun, because the JWT concept is pretty straightforward. Also, since I'm not writing a library for others to use, I was able to keep it lean and focused on what I wanted to do. However, while my JWT implementation doesn't cover the full spec, the tokens it generates are valid JWTs, so any external client will still be able to treat them as such. Here's the source, if you want to check it out.

Finally, in the last few days, I took the extremely messy "test client" that I built in literally 15 minutes a few days earlier and broke it out into the first draft of a real, documented public interface for future (golang-based) clients to "plug and play" with the auth system. I implemented a few very easy to use functions like VerifyAuthorization, which just takes the http.Request and does all the work to verify the access token, and even request a refresh if it is legally expired, and hand back a native go struct of an access token. There's also a HandleAuthorizationCode route implementation so that a client can delegate the whole auth code handshake to the library like so: http.HandleFunc("/api/authorize", client.HandleAuthorizationCode). This makes simple clients able to focus almost exclusively on logic, and completely delegate auth—which is great, because that was the entire goal.

There's still work to be done, but I do feel like this reached a real milestone to cap off the month. It was also really cool to have learned so much about web security, security hardening, all these auth protocols, and particularly the Go programming language. A lot of the web/auth stuff felt like nonsense that I was mostly trying to minimize and mitigate, but over the course of the month I found myself liking Go more and more. It feels like a bloaty-type language at first, but I came to appreciate just how small of a language it really is. Even so, there's still more to learn, but I'm excited to stick with it for more projects, and I've definitely found my python replacement for these web-server application projects.

And that's enough for now! After last year's false start, I'm happy to have completed a full adventure this year. Only 11 months to go until the next iteration :)

December 25, 2024

/stream /december-adventure

The past few days I've been chipping away at the next phase of this system, which was to go from the isolated /api/ routes into something that resembles an "authorization code flow" in the OAuth 2.0 speak. Essentially, if another website wants to use consent for authentication, it will direct someone's browser to consent, consent will verify that the person is a registered user, then redirect the browser back to the original site with an "authorization code", and then the original site can use that authorization code to retrieve access tokens.

This may seem roundabout, but having worked through trying to simplify it, it's necessary for a core security reason: the site that wants authorization is the one that needs to recieve the cookies with the access tokens. We also could have just implemented a login form on every website that wants auth and passed credentials through via a JSON API, but that both increases the surface area for problems and creates functional redundancies. This way, I implement login and authentication once, and every other web-system can just delegate all of that to consent while just worrying about the access tokens.

This is actually all implemented now, and I even built an (extremely messy) example client to test it all and prove it works. It works exactly like "log in with facebook" except with a tiny self-hostable Go program using sqlite instead of a giant international corporation, and it loads way faster.

A screenshot of a web login form. At the top of the page is a purple banner reading 'Pollinator Network'. Underneath is a heading that says 'login', followed by some text explaining what access is being granted. There is a text field for a 'handle', a text field for a 'secret', and a purple 'authenticate' button at the bottom. A horizontal line delineates the page footer, which reads '(c) ∞ Human Kind', though the 'c' is flipped horizontally.

The next step is to create a way for an instance of consent to register other applications for it to work with. Right now, I just pass an 'audience' and 'redirect_url' to the login page as query parameters, but this is a massive security hole: someone could craft a URL that asks you to log in with an audience of "private.service.com", but a redirect URL of "http://malicious.website.xyz/impersonate", and then basically have you log in to a valid service but hand the credentials back to an attacker. Since this auth server will always need agreement on who it is providing auth for, I'm going to make it so that you must pre-register your service (and its redirect url). For simplicity sake, it will probably just be a JSON file that an admin can edit: nobody who can't SSH into the server and edit a text file should be changing the authorized services.

One other cool thing that arose from this chunk of work is that I implemented a folder watcher for the HTML templates that are used to render the Login UI, and it rebuilds the templates while the server is running. This means that if I'm only making HTML changes, I can "rsync deploy" the templates without touching the rest of the program and hot-reload the UI. I'll probably do the same thing with that JSON file!

December 20, 2024

/stream /december-adventure

Managed to sit down and knock out the rest of this crypto/refresh work that I got stuck on a few days ago (after a very hectic final work-week of the year). Tonight I managed to get loading secret signing keys from disk working, generating a prime256v1 elliptic curve key using openssl, and encoded in the DER format. I then load that in via systemd's "LoadCredential" system, and parse the EC Private Key from those bytes.

With that now persistent signing key, I dug back through the way I was handling signing/verifying the auth JWTs, and realized I was trying to use the private/signing key to verify the token, where I should have been using the public/verification key. The golang-jwt library has this weird API where you provide the verification key inside a closure, and that hung me up for a while, but I finally figure out what it was asking for. If a search ever brings someone to this page, I finally managed to parse the jwt signed with a signingKey *ecdsa.PrivateKey like so:

						
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
	return &signingKey.PublicKey, nil
})
						
					

Anyhow, /api/login hands back a new refresh + access token pair, and /api/refresh will consume a given refresh token and hand back a new refresh + access token pair. This means the base functionality is complete and I can finally move on to the interesting parts of this project, which will be the parts needed to allow for this server to authenticate users across projects on n other domains.

December 16, 2024

/stream /december-adventure

In a few spare moments tonight, I tried taking the next step with refresh tokens, by decoding and verifying the token, and extracting the "sub" inside to reissue new tokens for. I ran into some confusion around some of Go's crypto packages, and could not get the verification on the token working before I had to step away.

December 14, 2024

/stream /december-adventure

A quick update for today's work on consent: /api/login now stores a refresh token in the database, /api/logout deletes those refresh tokens, and /api/refresh is stubbed out to check if the token is in the database. Just a quick session tonight!

December 11, 2024

/stream /december-adventure

Another few moments at the end of the day, this time finishing the JSON response for the /api/login route, and writing some routines to handle both the decoding of requests and the encoding of responses. I'm enjoying Go a bit more every day, though I'm still wary of its lack of robust type system as I perceive it in comparison to Rust. I don't find myself modeling my program through the type system as much as I do in Rust, but maybe that actually keeps things simpler. Either way, slow progress still being made on this, and I'm enjoying my time on it.

December 9, 2024

/stream /december-adventure

Only had a few minutes to chip away tonight, but I fixed an issue with a foreign key definition in sqlite, plugged in some simple test cryptography to the JWT signing, and got the tokens being returned as cookies on a successful test of /api/login. I then remembered that this auth system is for server <-> server interactions, and will use JSON and not browser tech like cookies, and deleted the cookie code. Either way, password registration and verification works, and the refresh/access tokens are being correctly signed and converted to strings.

December 6, 2024

/stream /december-adventure

On the train again, this time on the way home from Philadelphia. I did a bit more factoring of the project, and am feeling pretty confident on my ability to structure a project like this. Today I started working on a /login route, and began integrating JWT flows to support that. The login route isn't done yet, and I'm also learning more about the details of authorization and authentication. I no longer think that I'm actually going to build an "OAuth 2.0" server, but a very specific authentication/authorization flow for the speific types of web apps that I plan to be using this for.

December 4, 2024

/stream /december-adventure

Today on the train down to Philadelphia, I started fleshing out the project to have a more complete structure. I got a database interface set up, and an initial API router that handled a "/register" route. Not very interesting, but it accepts a handle/secret combo, hashes the secret with bcrypt, and stores it in the database. And that's it for now!

I'm working off of the Python code I already wrote for /cobalt, which makes it really quick and easy to translate to Go. Not much else to say about today's work, other than I'll be at a conference tomorrow and will likely not get any time for adventuring.

December 3, 2024

/stream /december-adventure

For today's leg of the adventure, I just set up the DNS for a new subdomain (auth.studiopollinator.com), started a new Go project that serves a 404 on "/", and then made sure my deployment scripts were functioning for local and remote deployments.

This didn't take too long, but it also gave me the opportunity to run through all of these steps a second time, since I've only done them all once for my recent work on api.studiopollinator.com. It also helped me check which parts of my deployment scripts from that project were not coupled with that project, and was the first step towards generalizing them.

December 2, 2024

/stream /december-adventure

Today I made some changes to how my VPS serves my web projects, and updated my local scripts to match. This integrates better with the way I plan to start deploying more executable services to the server, and which is how this month's adventure will take shape.

To be more specific, I have been so far just serving static sites out of my user's home directory on my VPS, so ~/www. This is annoying, because the way I "stage" my local projects to test on my machine, I place sites (correctly) in /var/www. This was forcing me to have multiple configurations for local vs remote deployments, and I realized that it was pretty unnecssary.

By aligning the way local and remote deployments work (both running NGINX configs that point to /var/www), my overall infrastructure deployment processes would be much simpler. Now that I aim to expand my projects beyond static sites and into server processes, I wanted to get this shift out of the way now. It also opens up the ability to create new users that can deploy to certain folders, since all of the server's content wont be in my personal home directory anymore. On a collaborative project, I can now also give deployment access to projects on a folder-by-folder basis.

Not quite necessary yet, but it allowed me to eliminate some lingering branches in some projects that had to start migrating to this new system earlier, and lets me keep momentum on my adventure.

December 1, 2024

/stream /december-adventure

A small bit of work to get ready for the adventure today: I laid out my map, and updated some of my scripts from my recently finished stripe "patronage" integration to make it easier to repurpose as a general "service deployment pipeline" to my web server. The first week of the adventure will probably be busy with some day-job work, and so my goal is to just make sure to at least get in 15 minutes of adventuring each day this week to start strong. Onwards!

November 29, 2024

/stream /woodworking /modular-shelf

When I started this project, I felt like I had more than enough maple in my studio to make all of the shelves. However, when I measured it all out it ended up being dangerously close. It took several hours choosing and arranging boards to both match well and provide enough depth to meet the overall requirements.

On top of a heavy duty work bench sets a stack of five glued up panels of hardwood, with unfinished edges and uneven ends. Some boards still have visible lumber yard markings on the end grain.

In the end, I managed to make it all work with only about an inch and a half to spare (of over 50" of total shelf depth). All of the shelves are 30.5" wide, and then the depth increases as they go down the shelf—the top shelf is just under 9" deep, and the bottom shelf is just under 12". These depths follow the angle of the front leg of the shelf, and protrude an additional 1/2" beyond the face.

On top of a heavy duty work bench sits a neat stack of five increasingly narrower panels of pale hardwood.

Doing the final milling on the glued up panels for the shelves was very satisfying, because I start with a messy pile of wood, and end up with a very clean and uniform stack of solid hardwood panels. These shelves were all milled to 3/4" as their final thickness, though I'll probably give them a final pass with a handplane later.

On top of an empty table, five solid maple shelves are standing up on their backs, with the shelf walls laying next to them in a similar manner so that it looks like the entire assembled shelf is resting on its back on the table. Several messy artist studio cubicles are visible behind the table.

While there is still a ways to go before the shelf can start actually coming together, with the shelves now at their final dimensions I was able to lay them out on a table to get an idea of the final massing of the piece.

November 22, 2024

/stream /woodworking /modular-shelf

With all of the framing cut and dry fit for the shelf, today I glued up the final assemblies for the sides and gave the joints a sanding.

A close up of a bridle joint between two perpendicular beams of wood. The grain is prominent.

I didn't really run into issues, but as I was finishing the glue up it gave me a chance to get very close to parts of the project that I have so far spent looking at from a distance. It can be strange to be focused on trying to get a five-foot beam of wood to be the right dimensions, and then get up very close to it and consider the small nicks and dents it might have picked up while being worked. As I complete more projects and raise my standards, I continue to find ways to raise the bar of quality and finish.

November 12, 2024

/stream /woodworking /modular-shelf

After cutting the mortises yesterday, today I cut the notches and wedges on the spans themselves. One note about the mortises that wasn't obvious until now is that the top of the mortise is angled through the beam in order to accommodate the slope of the wedge.

A close up of a vertical beam of wood with a rectangular hole cut into it. Right next to the hole is a perpendicular beam positioned as if it was just about to go into the hole, which is the perfect fit. On the under side of the horizontal beam is a shallow rectangular notch that is the same width as the vertical beam, and resting on the top of the beam in line with the notch is an angled wedge with rounded corners.

The notches were simple, in that I wanted 1" overhanging the joint, and the notches were 1" themselves (the thickness of the beam it goes through). They were only a 1/2" deep, since they only needed enough depth to "bite", and any deeper would have made the wedges start to look weird.

A close up of a vertical beam of wood with a horizontal beam roughly the same dimensions joined through it. A wedge sits on top of the horizontal beam through the vertical beam, locking it in place. A poured concrete shop floor can be seen in the background, out of focus.

The wedges were cut out of a scrap piece of cherry, and are the only "accent" species used in the entire project. (If I don't like the contrast when its all finished, I'll remake them out of maple.) I cut the rough shape out with my ryoba hand saw, and then rounded the corners on the belt sander. The rounded corners will help the wedge take blows from a hammer without cracking.

November 11, 2024

/stream /woodworking /modular-shelf

The final part of the "carcass" of this shelf is the wedged through tenons that give it lateral stability. The back legs of each side will have a mortise with headroom for a wedge, and then each through-tenon span will have a notch cut into them that the wedge locks into place.

A single five-foot long beam is held perpendicular to the floor by a hand just out of the top of the frame. Perpendiclar to the beam—and parallel to the floor—are two other beams that are connected to the vertical beam by through-tenon joints, at about one-third and two-thirds up the beam. A work bench, shop stool, and rolling trash can can be seen behind the showcased joinery.

What made this particular work challenging was that one of those back legs ended up being quite bowed after being ripped to size and resting in my studio for a few days; at the peak of the bow, it was 1/8" off alignment, which is significant when the space between the mortise and the edge of the beam is only 1/2". Instead of keeping that mortise centered and muscling the shelf together, I chose to actually offset the mortise so that bow was accounted for. The hard maple is not very yielding, and it felt like this would be the best option in the long run, save for cutting a new back leg (which I did not have the stock on hand to do).

Standing on its own are two sides of a shelf, connected by two perpendicular spans across the back at about one-third and two-thirds of the height. It looks like a tall shelf, but without any actualy shelves attached yet. A messy woodshop workbench sits behind the project.

In the end, the alignment worked out great, and I think the problem solving with the bow was better than finding more lumber and re-cutting the part. As it stands now, the whole unit is quite sturdy, even without any glue on the side pieces. It feels like my joint-cutting skills are finally getting pretty solid.

November 3, 2024

/stream /woodworking /modular-shelf

Found some more time again today to put my head down in the workshop and cut the joints for the second side of this shelf. Oftentimes, the hardest part of making progress is finding enough consecutive hours and the willpower to take on the next big chunk of a project.

Two tall, narrow sides of a shelf lean against a workbench. The floor is concrete, and a large glass garage door can be seen in the background.

October 26, 2024

/stream /programming /python /cobalt

For the past few weeks, I've been building up some full stack development skills by building a mutual credit web app with FastAPI via Python. When browsing the periodic table for other abstract minting metals to use as a name, I was reminded that cobalt has the periodic notation "Co", and couldn't resist. And so the app is called cobalt.

I originally set out to use this project to also learn more about React, but immediately found it to be incredibly overengineered for a solo project and decided to focus primarily on the backend. While I still have my gripes with Python, spending several dozen hours building a full sized application with it over a few weeks has definitely softened my attitude toward it, though I don't think it is quite above water on favorability yet.

I've also finally had a good excuse to actually write a meaningful database layer via SQLite (really like it) and SQLAlchemy (really hate it). I've finally written enough SQL now to be able to sit down and actually write non-trivial queries, which, while prickly, is kind of fun. I also implemented a JWT based auth system with access/refresh tokens and salted password hashing.

Finally, I'm getting to truly sit down and read Roy Fielding's famous REST Dissertation, as I've connected this project to some of my carried over interests in hypermedia that was borne out of my coalescent computer project. On that note, I've also been using it to dabble with htmx, which has been the most enjoyable thing I've ever used in terms of "javascript frameworks".

Anyhow, the application has all of its main functions working (account creation, authentication, peer connections, and transactions), and I'm now working on a more user friendly front end. Hopefully I'll have a live pre-alpha version up on the web soon.

October 21, 2024

/stream /woodworking /modular-shelf

After a bit of a break from this /modular shelf project, I finally took the next big step and cut the bridles and mortises for the first side piece. The slight angles on the ends of the beams were cut on a chop saw, but otherwise all of the joinery was cut by hand using my ryoba saws and bench/mortise chisels.

The side of a shelf, looking like a very tall capital letter 'A', leaning aginst a low half wall of an industrial studio nook.

September 20, 2024

/stream /woodworking /modular-shelf

To start off the day, I cut the prototype of the sliding dovetails for the shelves. I really liked the way this looked in the full size, though it's hard to tell how the tolerances will work in the hard maple vs the soft pine.

A close up of a sliding dovetail shelf slotted into the side rail.

Having felt like the prototype was successful, I then started milling the lumber for the final piece. It's been a while since I dressed a long, solid piece of maple, and I forgot how beautiful a smooth, seven-foot long board of hard maple is.

Two smooth, long, pale boards of maple resting on top of a large workbench.

After milling down the maple, I ripped it into the final two-inch by one-inch beams for the side pieces. After letting them rest for a few days, I'll cut the bridle joints and blind mortises that form the rest of the shelf.

Four long beams of hard maple rest against the wall of a messy shop studio, sitting next to the mockup of the final shelf siding they will become.

September 17, 2024

/stream /woodworking /modular-shelf

Continued working on the full scale prototype today. I really enjoyed mocking up this joint for the cross-brace beams for the shelf walls. It is essentially a half-lap splice joint in the long direction, with a shared notch between the two pieces where they overlap and pass through the mortise, which is then held snug by a wedge. There's a lot going on here, but it yields a clean, modular, and minimal aesthetic that really ties together the whole system.

A close up of a wood joint. Centered in the frame is a vertical beam, which has a rectangualar hole cut through it. Threaded through the hole are two perpendicular pieces of wood, that are fit snugly together as if they were one piece of wood. A small wooden wedge sits on top of the perpendicular pieces, also through the hole in the vertical beam.

September 16, 2024

/stream /woodworking /modular-shelf

Today I started—in earnest—a new project that I've been thinking about for over a year. I need new space in my bedroom for clothes/storage, and so I want to make a minimal, beautiful, modular, hard wood set of shelves. After sketching up some designs and testing some joints over the past few weeks, I set out to build a full scale prototype from scrap wood. I've never done a full scale prototype before, but considering the scale of this project and the number of questions I had about the construction, it felt like a very good investment of my time.

Laying on a large work bench are several long, narrow boards of pine, making the outline of the wall of a shelf. It is sligtly tapered on one side, square on the other. Next to it on the work bench are some measurement tools and additional wood scraps.

The idea for the shelf is this: there will be two elements to the shelf, which are the side walls and the shelves. The sides will be sort of an "A-frame" construction, and will have sliding dovetails cut into the beams to receive the shelves. They will also have mortises cut into them to receive two crossbeams near the bottom and middle, that will be notched and wedged to provide lateral support. The shelves themselves will be flat boards with the sliding tails on the edges that mate with the grooves in the sides. If all goes well, these will be dis/assemblable without and glue or hardware.

September 9, 2024

/stream /woodworking /cutting-board

After I spent so much time on the /cutting board earlier this year, I had forgotten that the initial reason I built it was so that I had another cutting board to use while refurbishing my old one, which was in need of some serious love.

Remembering to finally bring it with me to the studio, today I gave it a quick overhaul with my #4 plane to take out the knife marks and reflatten the surface. It took not much longer than 30 minutes to have the board look brand new, and with some nice rounded over corners.

On the edge of a large woodworking bench, a cutting board is pushed up against a clamped board, with a mallet and hand plane behind it and a pile of wood shavings next to it. The cutting board looks smooth and matte, with rounded edges and a roughly one inch hole centered at the far end.

After the nightmare of building the end grain cutting board followed by the ease of reconditioning this face grain cutting board, I'm not sure I see the appeal of ever building another end-grain board again. Any slight technical edge that the end grain has is easily overcome by the simplicity in construction and maintenance of a a face grain board.

Complete Stream →