Things are going pretty well in blog-engine land. I have technically gone live ever since I started tinkering on the deploy script, and I have nav in a workable styled state, and I also got this "admonitions" Marked extension working, for which the last post is honestly a bit of a stress test for; I don't find it very beautiful or polished, but it's working, has sane markdown syntax, and is open source. The main thing about it that's a little awkward for my use case is that it seems to increase the font size slightly, though that's completely sensible for the intended use case of callout blocks to emphasize content. Luckily these things place no restrictions on markdown content within them. I do expect at the first sign of actual difficulty I'll start making custom Marked extensions. I'm pretty impressed with the simple APIs available for that.
Currently I'm going over the CSS with a light touch, even though it's a very sensitive and important part of the end product, because I know I will do further passes on CSS handling later on. It's going to be exciting/dreadful/interesting to experiment and see whether it will be possible to effectively separate concerns in styling, so that I could do things like deploy a large list of already-curated markdown stylesheets for the overall blog post look and feel, while also independently tweaking how code renders (already got some ideas for that, and they largely involve tree-sitter) and having all these aspects of style separately controllable. CSS can be very well suited for these things.
My priority right now is on those nav anchor tags.
It was pretty easy to get the anchor links working for nav. It looks like this currently:
const renderLink = (html_href: string) => `<a href="/${html_href}">${html_href.replace(/^.*\//, '').replace(/.html$/, '')}</a>`;
function renderNav(navInfo?: NavInfo): string {
if (!navInfo) return '';
let nav = `<nav><div class="self">${navInfo.self.replace(/^blog\//, '')}</div><ul>`;
if (navInfo.prev) nav += `\n<li class="left">${renderLink(navInfo.prev)}</li>`;
nav += `\n<li class="up">${renderLink(navInfo.up)}</li>`;
if (navInfo.next) nav += `\n<li class="right">${renderLink(navInfo.next)}</li>`;
if (navInfo.children) {
nav += `\n<li class="children"><ul>${navInfo.children.map(cp => `<li>${renderLink(cp)}</li>`).join('\n')}</ul></li>`;
}
nav += `\n</ul></nav>`;
return nav;
}
I think that it would be much cleaner in the code and also more flexible if I adjust things so that markdeep is handled as a Marked extension. I could just define it as another language type and use tilde code block fences.
This thought prompts me to try even harder to see where the limits are with these markdown code fences because I keep coming back to trying to have these nested code block use cases.
Well how about that then, this is markdown "code". Can I render this out as code code?
```typescript
import * as util from 'util';
console.log('even with code fenced blocks inside?');
```
Looks **good** _so far_. `Inline code` should be a piece of cake.
The real test though: nesting both kinds of fences:
~~~
Here is a three-tilde fence (outer block has 4 tildes)
~~~
`````typescript
const crtnly = 'certainly';
console.log(`Here we have a five backtick code block fence. I ${crtnly} don't expect this template string literal's
backticks to cause any problems.`);
`````
How did we do?
That looks like it works as I expect (no surprising escapement from the outer quad-tilde fenced code block), and for completeness I will copy the above markdown verbatim into this doc:
Well how about that then, this is markdown "code". Can I render this out as code code?
import * as util from 'util';
console.log('even with code fenced blocks inside?');
Looks good so far. Inline code
should be a piece of cake.
The real test though: nesting both kinds of fences:
Here is a three-tilde fence (outer block has 4 tildes)
const crtnly = 'certainly';
console.log(`Here we have a five backtick code block fence. I ${crtnly} don't expect this template string literal's
backticks to cause any problems.`);
How did we do?
Not bad. The more I look at highlight.js code, though, the more dissatisfied I am with it. It's not terrible by any means but I have definitely been spoiled by tree-sitter.
Right, so in terms of evaluating, yeah I think fenced markdeep blocks inside markdown with Marked should be pretty feasible. I will proceed to rip markdeep out of the original foundational markdown abstraction flavors I designed this for, and lock myself into rendering this blog engine using Marked. I still think this project could grow into a wiki platform of some sort.
Meanwhile I do have nav working right now which is pretty sweet. It is very satisfying how fast the pre-rendered HTML pages load. Using simple anchor tags to hop around pages is very old school and I don't know if I will keep it as the standard paradigm but there is something to be said about letting your browser keep your scroll offset on the previous page, and other nice little quirks of doing it this "right way". Also a good bit of nostalgia in play here.
I realize that sending new files is a nice and effective way to instantly bypass CDN caching to avoid dealing with waiting for stale versions of content to be evicted. I may want to change my conventions around this slightly somehow. For example, I may want to make my final static pages render out to names (and hence URLs) that include a timestamp, it would cleanly prevent stale content from being viewed. Of course it's not a solution, because it will cause all links to pages to become insanely brittle. There may be a thread to pull on this for variously more integrated frontends though. There are a lot of ways to potentially efficiently communicate some "latest" state to remap plain resource names to the names that contain timestamps, which in turn guarantees the data fetched is at least as recent as that timestamp.
In the limit of the natural CDN cache interval being a day or so, and in the context of hosting content that only in rare scenarios requires precise control over publishing updated information, very little would justify engineering anything on this front. But I think this is still a loose end. If I publish a new page, someone who visited the site before (and even someone who is in the same geographical area as someone else who visited the site before) may not be able to receive the updated nav content, and won't be able to discover it until later because the stale version will continue to be served for some time.
The above hints at establishing a scheme to disseminate some metadata that specifies the latest versions for each page, but incorporating a service that can scale to high volume seems unavoidable. I think that because this data can also be served statically, I could simply mark this small quantity of metadata in the CDN for much shorter cache retention and handle the rest of the redirection logic in the page's frontend. For now though, I'm able to keep (aside from mermaid) the frontend free of any scripting, which is an equal parts mixture of masochism and minimalism.
What this thought experiment also revealed is a clerical detail that I want to flesh out the S3 sync logic slightly, to also clean
up, as my automated stage script already does in my dev environment, the files under stage/
not under files/
. This
way stale page content whose filenames I adjusted will not persist indefinitely within the S3 bucket. This S3 bucket represents the CDN's source of truth for the website in its entirety.
The above kinda complicates the code, though, so I'm electing to skip it. Realistically it just makes for little orphaned easter egg stale pages that people could discover. Not the end of the world.
I have been busy with incorporating code rendering features and old school page navigation autogeneration and CSS and other basic needs, so I have neglected one of the important things I outlined earlier as part of the design for this system, which is automated metadata. One of the most important pieces of metadata for an article on the Web I think is the date that it was written. Sometimes the date that it was most recently edited is also available, and I will strive to support both of these dates automatically for all posts made in this system without exception. Few things annoy more than to find an article about some very specific topic that interests me and then not getting any signal for how outdated that information might be.
Beyond this, I also have a long tail of potential ways to go above and beyond in metadata. They mostly do boil down to some presentation of edit history, and it certainly is unsubstantiated whether anyone really actually wants to dig so far into what I'm writing to desire such features, but as a coder I do have a lot of interest in this particular area of something that I might call "change analysis".
I could draw an analogy here to calculus, in which we extend the perspective of our analysis of numerical quantities from reasoning about these quantities directly, into reasoning about their change over time (or other suitable intervals), which is known as the derivative, or their accumulation over intervals, which is known as an integral.
These concepts can be thought of as steps up and down the rungs of a ladder in terms of perspective for analysis (hence "real analysis"). If we take the derivative of a set of values, and then compute the integral of that, then (after accounting for a constant factor) we get back the original input.
So too we can see something like a Git repository which stores the source code state of a software project and provides useful representations of how it evolves over units of change.
This topic will get its own article (or series, or shall I say saga) but the little notion I have for whetting your appetite on that is this:
Git provides us with mechanisms for storing (commit
) and differentiating (diff
) and
integration (patch
? sorta?) of the very complex quantity that represents a software project, but, just as the realization that twice differentiating the position of an object subject to a force over time yields a constant value proportional to the magnitude of that force, what insights might we be able to uncover from more deeply (or creatively) analyzing the changes?
I also need to integrate Giscus. I think it's time for that. It will be a good distraction from the autogeneration stuff I was working on recently. Producing post creation and update dates will be integrated with that and fairly dry.
I realized that one of the challenges with testing a traditional website with links is to ensure that they are consistent and correct. I am about to finalize the code to get nav HTML rendering working properly for these optionally-defined index.md intro pages for post sagas (I'm starting to think I might want to call them post trains instead of sagas...). I think this might already be a good time to put in place some simple playwright code to automate a simple site walker to validate my links.
I am going to hold off for now because I think it's still really easy to test manually and things are still a bit up in the air. Once the structure of the site is stable, though, I may want to do this because I have zero interest in periodically testing hundreds of autogenerated navigation links. And to imagine people used to manually write and edit these kinds of links...