|
| 1 | +--- |
| 2 | +title: new website powered by shake and pandoc |
| 3 | +published: 2025-07-16T00:12:03Z |
| 4 | +created: 2025-07-15T20:02:19Z |
| 5 | +aliases: |
| 6 | +- new website powered by shake and pandoc |
| 7 | +crossposts: |
| 8 | +- url: https://x.com/exodrifter/status/1945280659746119967 |
| 9 | + time: 2025-07-16T00:33:45Z |
| 10 | +- url: https://bsky.app/profile/exodrifter.bsky.social/post/3lu26aa3phs2v |
| 11 | + time: 2025-07-15T17:33-0700 |
| 12 | +- url: https://vt.social/@exodrifter/114860101285501596 |
| 13 | + time: 2025-07-15T17:34-0700 |
| 14 | +- url: https://www.patreon.com/posts/134222456 |
| 15 | + time: 2025-07-15T17:40-0700 |
| 16 | +- url: https://ko-fi.com/post/new-website-powered-by-shake-and-pandoc-N4N71I46YA |
| 17 | + time: 2025-07-15T17:46-0700 |
| 18 | +tags: |
| 19 | +- bad-shape |
| 20 | +- haskell |
| 21 | +- pandoc |
| 22 | +- shake |
| 23 | +- website |
| 24 | +--- |
| 25 | + |
| 26 | +# new website powered by shake and pandoc |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +Starting with [my last stream](https://vods.exodrifter.space/2025/07/08/1930), over the last week I've been hyper-focused on improving my website. I've lost quite a bit of sleep and missed a few meals, but I'm quite happy to say that my website is now written in Haskell, powered by the Shake and Pandoc libraries, and is verifiably Very Cool. |
| 31 | + |
| 32 | +The website used to be [powered by Quartz and Nix](20240916090424.md), a setup that I found myself frequently frustrated with. The Nix part of the project was fine -- though I still need to learn Nix more fully. The Quartz part of the website, however, was a major source of the frustration. It has the Bad Shape. |
| 33 | + |
| 34 | +# the Bad Shape |
| 35 | + |
| 36 | +In general, with programming, there's a particular kind of "shape" that I really dislike. This shape is hard to understand, hard to maintain over long periods of time, and hard to customize. I like to call this "shape" the Bad Shape, and [I've briefly written about it before](20230321174710.md). This is in contrast to the "pipe" shape, which is easy to understand, easy to maintain, and easy to customize. I think pipes are the best kind of shape for a program to have. |
| 37 | + |
| 38 | +Visually, the difference between the two looks like this: |
| 39 | + |
| 40 | + |
| 41 | + |
| 42 | +I've found myself showing this particular image to others a lot when I explain my design sense when it comes to programs and APIs. When you're programming, you can imagine that each part of the program is a piece that can snap neatly into other pieces, like Lego bricks or sockets. |
| 43 | + |
| 44 | +I find the process of creatively snapping pieces of functionality together to be really enjoyable. With pipes, as long as the input and output types match, you can connect them or change them in or out for other pipes without any issue. You can do whatever you want at any step along the way. |
| 45 | + |
| 46 | +The problem comes when you need to snap pieces together that need predefined shapes. The Bad Shape is when you have some kind of "wrapper" around the pieces that you want to use. No longer can you snap together whatever piece you want that happens to have a matching type; now, because the Bad Shape defines both the input and output constraints, you have to make the exact piece that the Bad Shape wants. |
| 47 | + |
| 48 | +## concrete example |
| 49 | + |
| 50 | +What this means, concretely, is that the Bad Shape is harder to use. Consider, for example, this typescript code from my old website: |
| 51 | + |
| 52 | +```ts |
| 53 | +let repo: Repository | undefined = undefined |
| 54 | +return async (_tree, file) => { |
| 55 | + let date: MaybeDate = undefined |
| 56 | + |
| 57 | + const fp = file.data.filePath! |
| 58 | + const fullFp = path.isAbsolute(fp) ? fp : path.posix.join(file.cwd, fp) |
| 59 | + for (const source of opts.priority) { |
| 60 | + date ||= file.data.frontmatter[source] as MaybeDate |
| 61 | + } |
| 62 | + |
| 63 | + file.data.dates = { |
| 64 | + created: coerceDate(fp, date), |
| 65 | + modified: coerceDate(fp, date), |
| 66 | + published: coerceDate(fp, date), |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +In brief, what this function does is extract date information from a Markdown's frontmatter. However, it's shaped in a very specific way: |
| 72 | + |
| 73 | +- It only takes a `file` as input, which is a parsed Markdown file. |
| 74 | +- It changes the state of the `file` to have the same date for the `created`, `modified`, and `published` date. |
| 75 | +- It doesn't return anything. |
| 76 | + |
| 77 | +It's written this way because that's how "transformers" are defined in Quartz. I have no freedom to change the input or output types, and furthermore **I have to understand the machinations of Quartz** in order to implement this function. This partially explains why I return the same value for `created`, `modified`, and `published` -- it's because of what Quartz does with those dates later, after my function has run. |
| 78 | + |
| 79 | +Suddenly, I'm required to understand the changing mutable state of the entire Quartz program. This happens whenever Bad Shapes are involved. Instead of being able to focus on the individual transformation I want to make, I now have to understand much more about what the program does before it gets to my function and what it does after my function runs. |
| 80 | + |
| 81 | +This increases the mental workload, increasing the difficulty of writing and maintaining the program. It also becomes less fun! |
| 82 | + |
| 83 | +## counterargument |
| 84 | + |
| 85 | +Of course, I would be remiss not to mention that Bad Shapes are sometimes necessary and "good, actually". If you're already familiar with Quartz, you might notice that the code I posted earlier looks a lot like a `QuartzTransformerPlugin`, and that's because it is. Bad Shapes like to turn into the plugin pattern, and sometimes plugins are a good solution to a problem despite the fact that they impose limitations on what you can do. |
| 86 | + |
| 87 | +However, "avoid bad shapes" is one of the design principles that I try to follow, along with "avoid shared mutable state". You can't avoid either completely, but I think they're good rules of thumb. |
| 88 | + |
| 89 | +This is why I like functional programming so much; since most libraries and functions are just "pipes", it lets you compose functionality easily, increasing how enjoyable it is to write programs. Another example on the "good" end of this spectrum are unix-style commands. `cat` prints a file, `tail` lets you get the last few lines of the input, and `cowsay` lets you print an ascii cow saying whatever the input was: |
| 90 | + |
| 91 | +``` |
| 92 | +$ cat content/support.md | tail -n 1 | cowsay |
| 93 | + _________________________________________ |
| 94 | +/ I also love it when you share the stuff \ |
| 95 | +| I've made that you like with your | |
| 96 | +| friends and post nice comments on the | |
| 97 | +\ things I make. <3 / |
| 98 | + ----------------------------------------- |
| 99 | + \ ^__^ |
| 100 | + \ (oo)\_______ |
| 101 | + (__)\ )\/\ |
| 102 | + ||----w | |
| 103 | + || || |
| 104 | +``` |
| 105 | + |
| 106 | +This works because the types match! And you can change up the pipeline as much as you want. |
| 107 | + |
| 108 | +## it's Bad Shapes all the way down |
| 109 | + |
| 110 | +However, static site generators (or SSGs for short) are particularly rife with this problem. If you try to find a static site generator to use, they all _prescribe_ some kind of workflow, because they're trying to get you to provide the pieces that the Bad Shape wants. I feel like SSGs like to embrace the Bad Shape... and it makes me feel disappointed in software. You end up having to spend a lot of time reading the SSG documentation to know how things work so you can make it do what you want, and that sucks! It's like you have a box that does... something, and [you just have to pull random levers until it starts working](20240109152210.md). Writing or using software could be a lot more fun if I didn't have to do that all the time. |
| 111 | + |
| 112 | +Quartz, in particular, takes this to an extent that I found a little hard to believe when I first started using it. It does the typical things that most other SSGs do, like requiring that you put all of your content in a specific `/content` folder. But, it takes the Bad Shape pattern even further. To use Quartz you have to literally clone the repository and make your own changes ontop. I'm not making this up, that is how they tell users to [get started](https://quartz.jzhao.xyz/#-get-started): |
| 113 | + |
| 114 | +> Then, in your terminal of choice, enter the following commands line by line: |
| 115 | +> |
| 116 | +> ```sh |
| 117 | +> git clone https://github.com/jackyzha0/quartz.git |
| 118 | +> cd quartz |
| 119 | +> npm i |
| 120 | +> npx quartz create |
| 121 | +> ``` |
| 122 | +
|
| 123 | +The content folder, custom plugins, custom components, _even the configuration file_ have to be modified directly inside of the repository. Hopefully this makes it abundantly clear why I didn't like working with Quartz. |
| 124 | +
|
| 125 | +# what's changed |
| 126 | +
|
| 127 | +The website looks very similar to what the site looked like a week ago -- the styling is mostly the same, but as you may have noticed by now, the biggest visual change is that elements on the page are actually aligned with the dot grid. |
| 128 | +
|
| 129 | +On the old website, the dot grid was static and didn't move when you scrolled the page. Elements on the page also didn't have a grid size. Now, almost[^1] every single element on the page is sized in an exact manner such that everything stays aligned to the grid, through to the bottom of the page. This helps invoke the sense that you're reading a dot grid bullet journal, which was the original desire for the background. If you're interested in seeing how this is done, the [style.css](../style.css) is thoroughly documented. |
| 130 | +
|
| 131 | +Data is also generally more consistent now. Almost[^1] every page has well-formed and well-defined timestamps, every tag uses `kebab-case`, and every index page contains a listing of files -- including the home page. |
| 132 | +
|
| 133 | +There are several things that are missing, notably: |
| 134 | +- No search function. |
| 135 | +- No description in RSS feeds. |
| 136 | +- Backlinks are missing. |
| 137 | +- Code syntax highlighting. |
| 138 | +- No dark/light theme toggle. |
| 139 | +
|
| 140 | +I plan on fixing these issues slowly over time. However, the software I wrote to build this website is much easier for me to maintain and extend, opening the doors for further modifications and features which were previously impossible or difficult to do: |
| 141 | +
|
| 142 | +- Unify the VOD website with this website, so notes can link to VODs directly and vice-versa. |
| 143 | +- Compress/resize images on build to reduce the size of pages. |
| 144 | +- Check to make sure that there are no broken internal links. |
| 145 | +- Add RSS feeds for every tag, so you can get notifications about a specific thing if you want. |
| 146 | +- Export the notes in different formats, like the original Markdown source and JSON. |
| 147 | +
|
| 148 | +But, I think a week of near-constant working is enough for now. I need to get back to taking care of myself, telling people that my game [_no signal_ is coming out](20250625201455.md), and looking for jobs. |
| 149 | +
|
| 150 | +I hope you enjoyed this write-up and like the new website! I'm looking forward to making the site better in the future ❤️ |
| 151 | +
|
| 152 | +[^1]: I still have some bugs I need to fix. I'll get to them! pinky promise? |
0 commit comments