2024-06-22

Condensing updates that appear in digests


In the previous post, I claimed to be "finished" my exploration of handling updates.

Ha!

I don't like how my update notifications appear in digests — in e-mail newsletters that gather up all the posts over a period of time.

There are two problems:

  1. Update notifications appear in digests as separate, cookie-cutter posts, which bury and dilute "real" posts.

    On websites, we can make synthetic update posts visually distinct to help guide visitors past them. We could do that in HTML mail digests too, but I think update posts will still put people off actually reading new material when they are going quickly through their e-mail, particularly when updates are more recent and therefore come before the "real" posts.

  2. Updates appear even for posts that are included in the current digest.

    feedletter digests always include the most recently seen version of a post, so from the digest reader's perspective, the "update" notification tells them about nothing that isn't already in the post.

To address this, I mean to do a bunch of things.

  1. I'm going to modify the definition of iffy:synthetic, reversing course on my my current admonition that

    Applications that include iffy:synthetic as a direct child of channel SHOULD NOT also mark individual items as iffy:synthetic, unless there is some meaningful sense in which some items are more synthetic than others. It serves no purpose to mark every item of a feed iffy:synthetic when the channel is already so marked.

    Never mind.

    Until now, iffy:synthetic has been basically just a marker, so there was no point in marking posts twice. Marking at the channel level implied the syntheticness and the types at the item level.

    But now we'll reconceive of iffy:synthetic as also the carrier of the data on which the item content (in description, content:encoded, or atom:content) is based.

    The item content, of course, remains the post's HTML, whether it is a synthetic post or not. But we'll want the information we used to compose synthetic posts to be available, in an iffy:type-dependent way, inside item > iffy:synthetic. So even when a channel is marked synthetic, with a type that implies the type of elements, we will want explicitly to include an iffy:synthetic element in each item.

    For synthethic items of type UpdateAnnouncement — the relevant case here — we've already defined an element that carries most the relevant data, iffy:update.

    We'll also need the guid of the post updated. So we'll bring back iffy:original-guid, with a slightly modified role.

  2. I'll define a feedletter content Customizer that

    1. filters away upates to items already in the post;
    2. combines the remaining updates into a single notification post; and
    3. places that notification post at the end of the digest.

So. We have a plan!

Let's see if it works.

2024-06-20

Updated: Sprouted


A significant update of Sprouted was made on 2024-06-20 @ 05:50 PM EDT.

→ Add bold update crediting Maggie Appleton for some of these ideas.

The post was originally published 2024-06-20 @ 03:05 PM EDT.

Sprouted


➣  This post was meaningfully revised at 2024-06-20 @ 05:50 PM EDT. The previous revision is here, diff here. (See update history.)

I've finally "finished" my reaction to Chris Krycho's provocation, Feeds are Not Fit for Gardening. (ht Erlend Sogge Heggen).

I think I now have infrastructure sufficient to publish evolving content, that might start as an unfinished sketch and "blossom" over time (and with help and interaction from you, dear reader).

The approach I've taken — of course since I am an RSS fanboy — is to graft what I need on top of existing RSS infrastructure. Among RSS infrastructure, I include both conventional feed readers and "announcers": RSS consumers that notify parties about new items in ways that may be more disruptive than just another item in a well-populated feed.

An example on an announcer is my own feedletter, which notifies RSS items by e-mailing posts one at a time or in digests, and also by posting to Mastodon.

My approach is quite manual. No software determines when an "update" has been made. "Tweaks" — which may include correcting typos, reworking sentences, or even rewriting whole paragraphs without changing the intended meaning — shouldn't be notified as updates. Modifications, however big or small, that substantially augment or alter the content of the item usually should be notified. In my view, which is which is the author's judgment call. So authors manually add an item to their post's update history when they wish to provoke such a notification.

Once authors begin to define an update history, that history becomes appended to the end of posts. Example.

When authors declare a meaningful update, all they must provide is a timestamp. An atom:updated element will be included in the RSS item with that timestamp.

Authors also may provide a description, a "revision-spec" (usually a git commit identifier) for the revision superceded, and an author list (if authorship as well as content is changing with the update). Example.

The more authors provide, the richer the update history. Update histories will include any description given, along with links to the prior ("superceded") revision, and a diff between the updated post and the superceded revision.

Whenever a new update is declared, a synthetic post will be produced, whose publication date is the moment of the update, announcing the update. Example.

That post will have its own RSS guid and permalink. So, without requiring any change to the behavior of existing feed readers, followers will be notified of these updates.

The RSS items generated for these updates will be marked "synthetic", of "type" UpdateAnnouncement, so that announcers (for the moment only my feedletter, but please join in!) can decide whether they wish to notify these items.

They include a "hint" for announcers to adopt a Piggyback policy, meaning the synthetic update notifications should be included in digests and other multi-post announcements that include at least one not-piggyback post, but they should not trigger announcements on their own. Announcers are free to follow this hint, or to make a different choice.

For subscribers to notication services that accept the hint, synthetic update posts will not cause people to get spammed with "here's an update!" messages. That would be annoying. On the other hand, e-mail subscribers will not be notified of all updates to existing posts. One-at-a-time subscribers will see none of the update notifications. Digest subscribers will see update notifications only when the digest would also include at least one "organic" post. There is a trade-off to navigate between annoyance and information.

For posts intended to evolve over time, that might invite the attention of particular followers or collaborators, authors can explicitly declare a post to be a "sprout". Example.

The term is taken from Chris Krycho's essay. These posts will be accompanied by their own RSS feeds (example), and will track declared updates only to that particular item. The implementation is not great — ideally each notification would include and highlight the changes — but so far my diffs are too lame for that. However, subscribers will be notified, via a dedicated feed, of each update to evolving items that they mean attentively to follow.

Obviously, nothing is really "finished". Everything in life is a garden!

But I think that this is enough that I can start sketching and evolving posts intended to develop over time, rather than finished-product "atomic" posts.

Thanks to Chris Krycho and Erlend Sogge Heggen for inspiring me to think about all this!


Update: Chris Krycho credits Maggie Appleton for some of these ideas, so I will too!

Updated: Feedletter tutorial


A significant update of Feedletter tutorial was made on 2024-06-20 @ 01:10 PM EDT.

→ Add note to Section 16, "Advanced: Customize the content" documenting feedletter API changes that slightly modify this section of the tutorial.

The post was originally published 2024-01-29 @ 10:30 AM EST.

2024-06-16

HTML iconography


➣  This post was meaningfully revised at 2024-06-17 @ 02:35 PM EDT. The previous revision is here, diff here. (See update history.)

My web skillz are very old-school.

I only recently learned we're not supposed to use <tt> anymore. (<code> is what the kids use.) We're not supposed to use <a name="whatev"> for our in-document link targets. We should just use <a id="whatev">.

(To be fair, it's pretty cool the targets don't have to be <a> tags any more.)

Anyway, back in my day, to add little icons that might represent your website, we just added a 16x16 pixel /favicon.ico file in some weird, nonstandard Microsoft image format.

Thank you Internet Explorer, the very first evil internet silo that kids these days have never encountered!

My ancient "interfluidity main" site has one of those old-school /favicon.ico files, and I'm not messing with it. But I thought I'd add fresh icons for this site and interfluidity drafts. One 16x16 icon file isn't enough for the modern world. Your site might need an icon on a phone, a tablet, a watch, whatver. Android and Apple devices treat icons differently. Firefox, I discovered, chooses icons differently than other browsers.

The best resource I found to help make sense of the brave new world of website icons was an article by Mathias Bynens.

That article's last update was in 2013, so maybe it's not current? It's a decade newer than my old habits, so hey.

I used Affinity Photo to take the photo I use as an avatar on social media, and label it, for this website as "tech". For prettiness as icons on mobile devices, I also needed to give it rounded corners. I wanted to select a rectangle, round the corners, then invert the selection, and delete to transparent to make rounded corners.

That'a basically what I did, but there's nowhere to set a corner radius on a straight-up rectangualar selection in Affinity Photo.

However, there is a rounded rectangle drawing tool, which draws on its own layer, and — very useful to know! — there is a Selection From Layer menu item, that converts a shape drawn in a layer to a selection. Once I had my selection, invert and delete was no problem and I got my rounded corners.

I gather one can omit rounding corners oneself, if you only care about Apple devices. Apple defines apple-touch-icon and apple-touch-icon-precomposed, and if you supply the not-precomposed version, devices should round corners and maybe drop shadow to "compose" your icon.

Most resources I looked at suggested taking control, so you know what you will get and can use the same icons crossplatform, so that's what I did. So, I rounded my own corners yee-haw!

Then I exported my image as a PNG in all of the sizes recommended by the Bynens article, stole his recommended HTML snippet, and added it — with some modification, see below! — to the main layout of my unstatic-based static-site generators.

    <!-- icons / favicons -->

    <!-- we just want the squared-corner image with no overlays for traditional favicon uses at tiny sizes -->
    <!-- swaldman added, ick, firefox scales down the biggest size for its tab icon, so we use the graphic we want for small sizes as the largest... -->
    <link rel="icon" type="image/png" sizes="500x500" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-500x500.png"> 
    <link rel="icon" type="image/png" sizes="32x32" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-32x32.png">     <!-- swaldman added, for standard favicon size -->
    <link rel="icon" type="image/png" sizes="16x16" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-16x16.png">     <!-- swaldman added, for standard favicon size -->
    <link rel="icon" type="image/png" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-57x57.png">                   <!-- swaldman added, for small icons by default -->

    <!-- at bigger sizes, we overlay a bit of text -->
    <!-- icons as recommened by https://mathiasbynens.be/notes/touch-icons -->
    <!-- For Chrome for Android: -->
    <link rel="icon" sizes="192x192" href="<( iconLoc.relative )>/interfluidity-wave-tech-192x192.png">
    <!-- For iPhone 6 Plus with @3× display: -->
    <link rel="apple-touch-icon-precomposed" sizes="180x180" href="<( iconLoc.relative )>/interfluidity-wave-tech-180x180.png">
    <!-- For iPad with @2× display running iOS ≥ 7: -->
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="<( iconLoc.relative )>/interfluidity-wave-tech-152x152.png">
    <!-- For iPad with @2× display running iOS ≤ 6: -->
    <link rel="apple-touch-icon-precomposed" sizes="144x144" href="<( iconLoc.relative )>/interfluidity-wave-tech-144x144.png">
    <!-- For iPhone with @2× display running iOS ≥ 7: -->
    <link rel="apple-touch-icon-precomposed" sizes="120x120" href="<( iconLoc.relative )>/interfluidity-wave-tech-120x120.png">
    <!-- For iPhone with @2× display running iOS ≤ 6: -->
    <link rel="apple-touch-icon-precomposed" sizes="114x114" href="<( iconLoc.relative )>/interfluidity-wave-tech-114x114.png">
    <!-- For the iPad mini and the first- and second-generation iPad (@1× display) on iOS ≥ 7: -->
    <link rel="apple-touch-icon-precomposed" sizes="76x76" href="<( iconLoc.relative )>/interfluidity-wave-tech-76x76.png">
    <!-- For the iPad mini and the first- and second-generation iPad (@1× display) on iOS ≤ 6: -->
    <link rel="apple-touch-icon-precomposed" sizes="72x72" href="<( iconLoc.relative )>/interfluidity-wave-tech-72x72.png">
    <!-- For non-Retina iPhone, iPod Touch, and Android 2.1+ devices: -->
    <link rel="apple-touch-icon-precomposed" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-57x57.png">

    <!-- end icons / favicons -->

A complication emerged, in that my text-labeled icons looked busy and bad, and the text was illegible, when rendered at very small sizes. So you'll note that, for small sizes, I use interfluidity-wave-blank-square files rather than interfluidity-wave-tech. (I thought the very small icons looked better with square corners as well.)

But Firefox kept picking up the largest <link rel="icon" ... > and downsampling from that, rather than downloading the nearest or nearest-larger icon.

So I added the image I want used only for small icons also as a very large icon.

    <link rel="icon" type="image/png" sizes="500x500" href="<( iconLoc.relative )>/interfluidity-wave-blank-square-500x500.png"> 

Less quirky browsers hopefully never choose this to render from, because there is always a better-sized icon to choose from. But Firefox does choose this one and downsample to render its very small icons-in-a-tab, so the trick gets rid of the ugly, illegibly scaled text in tiny icons under Firefox.

(It does seem a bit wasteful to trick Firefox into downloading 500x500 images to render at 16x16 or 32x32, but if it smartens up, it can download icons prerendered in just those tiny sizes!)

Anyway, that was what I did to add icons to this site and to drafts.

Please let me know if there are much better ways!


Update (17-June-2024):

Carlana Johnson points me to a great article by Andrey Sitnik, How to Favicon in 2024: Six files that fit most needs.

For now, because I'm lazy, and because my icons are not SVG-friendly, I'm leaving things as they are.

But perhaps someday I'll make better, vector, logos and icons, rather than just repurpose my social media avatar. Then I will try out this carefully thought-out approach.

2024-06-08

Should blogs adopt the itunes:category RSS tag?


Apple organized a whole slew of standard categories or genres for podcasts, when they defined the itunes RSS namespace for podcasts. This helped discoverability of podcasts, as podcast applications and indexers can let users search or browse by genre, or make suggestions based on genres users seem to prefer.

Apple seems to have done a pretty good job at this. It's not obvious that "podcast genres" are meaningfully distinct from "blog genres". We could, of course, invent some analogous kind of categorization just for blogs, but why? As Dave Winer hath writ:

Fewer format features is better

If you want to add a feature to a format, first carefully study the existing format and namespaces to be sure what you're doing hasn't already been done. If it has, use the original version. This is how you maximize interop.

Podcasts got a huge lift from what was originally the blog-centric RSS format. Why haven't blogs adopted podcast-RSS best practices to get a lift right back?

There's a potential issue that some applications may use the presence of itunes RSS tags to imply an RSS feed is for a podcast. But that's pretty dumb. If applications expecting podcasts import blogs without soundfiles because they use this heuristic, well, bad on them. They should fix that. When blogs do contain some posts with audio <enclosure/> elements, then arguably they are podcasts inter alia. Client applications should use intelligent criteria to decide what they want to consider suitably a "podcast" or "podcast episode".

It strikes me as a good idea to make use of good ideas from the itunes (and podcast) namespace for blogs and other RSS applications.

Starting, perhaps, with itunes:category.

Apple defines itunes:category as a channel-level element that permits multiple entries (you don't have to be just one genre), and nested entries for subcategories. Seems pretty good!

What do you think?

2024-06-06

Neonix


➣  This post was meaningfully revised at 2024-06-06 @ 06:30 PM EDT. The previous revision is here, diff here. (See update history.)
➣  This post is expected to evolve over time. You can subscribe to ongoing updates here.

I've found ripgrep to be an invaluable tool. At some level it's just grep, but its speed and ergonomics make it something else. I find things much more quickly. In combination with projectile, it gives me a fast project-wide find, reducing one of the advantages of commercial IDEs over my humble emacs.

Today, Bill Mill points me to a command line tool called fzf which looks like kind of a command-line Swiss army knife. It certainly makes sorting through very long find . output a breeze.

Some of Bill's scripts use a find replacement called fd, which I plan to take a look at.

I think this is an interesting trend, taking venerable UNIX command-line tools and rethinking, reimplementing them with modern languages and the decades of experience since that first, revolutioary, burst of command-line creativity in the early UNIX days.

I'll let this post become a "sprout" from which I can track these kinds of tools as I encounter them.

  • fd
    A modern retake on find I haven't played with yet.

  • fzf
    A fuzzy-matching tool for interactively sorting through large command line and command completion outputs. See Bill Mill.

  • rg
    "ripgrep". A new take on grep, super fast, seachers directories recursively, by default excluding .git and whatever is .gitignore-d.

I'll add more as I, um, fd them!


p.s. apparently there's a DJ called Neonix! Sorry! I'm using the, er, neologism to refer to neo-UNIX.


Update 2024-06-06: Kartik Agaram's points me to Bill Mill's "modern unix tools" page. Which itself contains a link to a "Modern Unix" collection by Ibraheem Ahmed. So much to play with!

2024-06-05

Readying a blog for revision histories and sprouts under unstatic


➣  This post was meaningfully revised at 2024-06-06 @ 01:30 PM EDT. The previous revision is here, diff here. (See update history.)

I've been developing support for my take on Chris Krycho' "sprouts" against this blog. Much of that support is now built into unstatic, my library for building static-site generators. But it does also require some support from within applications of that library, from the scala code and the untemplates of the individual site generators.

I'm going to upgrade my "drafts" blog to support revisions, diffs, and sprouts. I'll document what it takes to do that here.

Enable revision- and diff-generation in Scala code

In the object DraftsSite, the unstatic.ztapir.ZTSite that defines the site to be generated, inside the unstatic.ztapir.simple.SimpleBlog that defines the blog, add a RevisionBinder that can pull old revisions of pages and generate them into the website, and a DiffBinder that can generate diffs between current and new revisions:

 override val revisionBinder : Option[RevisionBinder] = Some( RevisionBinder.GitByCommit(DraftsSite, JPath.of("."), siteRooted => Rel("public/").embedRoot(siteRooted)) )
 override val diffBinder     : Option[DiffBinder]     = Some( DiffBinder.JavaDiffUtils(DraftsSite) )

By default, SimpleBlog sets these values to null. We override them.

The RevisionBinder we are using is RevisionBinder.GitByCommit. Its constructor accepts

  1. our ZTSite;
  2. a file path (java.nio.file.Path) to the git repository in which revisions are stored, just '.' for us because the git repository is the static-site generator's working directory;
  3. a function that converts a site-rooted path (unstatic.UrlPath.Rooted) into the associated path within the repository relative to its root (as unstatic.UrlPath.Rel);
  4. A RevisionBinder.RevisionPathFinder, a function which takes a document's site-rooted path and a "revision spec" (which for this revision binder is a full-size hex git commit) and determines the path the revision should take within the site.

We omit the fourth argument because we use a default, which coverts a path like /a/b/whatever.html to /a/b/whatever-oldcommit-c6e71f4d689f2b208c3eae19e647435322fa6d04.html

For a DiffBinder, we use DiffBinder.JavaDiffUtils, based on the java-diff-utils library. When we ask it to generate a diff for a path, we give it a reference to the RevisionBinder.RevisionPathFinder so it can know the filenames old versions get generated into. We also give it a DiffBinder.DiffPathFinder, which computes the pathnames of the generated diffs. Again, the DiffBinder.DiffPathFinder is omitted our code. We rely a default argument, which produces diff paths like /a/b/whatever-diff-72eaf9fdfebc9e627bff33bbe1102d4d250ad1d0-to-199e44561de3fd9e731a335d8b2a655f42d9bc04.html.

Now, if we ever provide update histories to any posts, copies of any old revisions referenced will be generated into the public directory of the site, as well as diffs between adjacent items in the update history.

Modify the site to generate update histories at the end of posts

It's a matter of taste, but we'll display update histories only on single-post permalink pages, not at the end of each post when concatenated together. And we won't include them as content in RSS. (Update histories do get included as additional metadata in RSS. That's built in.) SimpleBlog conveniently distinguishes between Single, Multiple, and Rss; we can just check our presentation and behave appropriately.

So... We'll

  1. Steal layout-update-history.html.untemplate from the tech blog, and bring it in as a layout of drafts. (I had to import com.interfluidity.drafts.DraftsSite.MainBlog, and modify the link in the note to point to the drafts got repository, rather than the tech rep.)
  2. Modify layout-entry.html.untemplate in drafts to bring in the new layout of update history. That turns out to be really easy, because we already have logic at the end of our entry layout to restrict addition of previous and next links to single page presentations. So all we have to do is add our update history layout just after the div for those links, but within the conditionally added region. It's literally just
    <( layout_update_history_html( input ) )>
    

    inserted just after that div, still within the conditional region.

Modify the main layout and CSS so that old revisions are visually distinct from, and link back to, current revisions

At the top of the body element of layout-main.html.untemplate, we add an empty div element called top-banner.

  </head>
  <body>
    <div id="top-banner"></div>

In current revisions, this will remain invisible and empty. But we'll add a bit of javascript to detect if we're in an old revision, and add some HTML with a link back to the current revision. If we are in an old revision, we'll also add a class called old-draft to the body element, so that we can do whatever we feel like in CSS to make the old revision visually distinct.

We use a javascript regular expression and our current location to decide if we are in an old revision.

    <script>
      document.addEventListener("DOMContentLoaded", function() {
          const regex = /(^.*)\-oldcommit\-[0-9A-Fa-f]+\.html/;
          const match = window.location.pathname.match(regex);
          if (match) {
              const b  = document.querySelector("body");
              const tb = document.getElementById("top-banner");
              b.classList.add("old-draft");
              tb.innerHTML = "You are looking at an old, superceded version of this page. For the current version, please <a href=\"" + match[1] + ".html\">click here</a>.";
          }
       });
    </script>

We adjust our main CSS to keep the top-banner div at the top of our document, when it's relevant:

body.old-draft #top-banner {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    color: black;
    background-color: yellow;
    text-align: center;
    font-family: 'RobotoCondensed', 'Arial', 'Helvetica', sans-serif;
    font-variation-settings: "wght" 500;
    padding-top: 4px;
    padding-bottom: 4px;
    border-bottom: 2px solid black;
}

Also add CSS so that, when viewing old revisions, the documents look, well, old.

body.old-draft {
    padding-top: 1em;
    background-color: #F3F5DA;
    color: #6E7FD9;
    font-family: 'GabrieleD', 'Courier';
}

(These choices were inspired by the TT2020 image here, although ultimately I went for Gabriele, because the TT2020 file sizes were very large.)

Add a prologue to posts with revisions or that generate sprout RSS

When a post is a revision or a sprout, we want a prologue that indicated that it is, with links to the prior revision, the update history, and the sprout RSS.

I'm too lazy to describe what it took to add that in detail, but here's a nice, concise commit. Check out the diff.

Miscellaneous tweaks

I don't want to have to import UpdateRecord whenever I want to add update histories to entries, so I added them as an extra import to my untemplate customizer in my mill build file, build.sc:

  override def untemplateSelectCustomizer: untemplate.Customizer.Selector = { key =>
    var out = untemplate.Customizer.empty

    if (key.inferredPackage.indexOf("mainblog")>=0 && key.inferredFunctionName.startsWith("entry_")) {
      out = out.copy(extraImports=Seq("unstatic.*","com.interfluidity.drafts.DraftsSite.MainBlog","unstatic.ztapir.simple.UpdateRecord"))
    }

The "update history note" should be small, so I add to css:

.update-history-note {
    font-size: smaller;
    line-height: 100%;
}

Republish the site

Even though nothing visible should change, let's go ahead and republish the site, so that our javascript and css scaffolding for old-looking updates become available.

Test and tweak

Even though I don't have any actual new revisions to create, I added a fake revision history to the most recent post, played around in CSS with the look of the old revision until I liked it, then commented away the fake update history.

2024-06-02

Green shoots of sprouts


Erlend Sogge Heggen pointed me to a post by Chris Krycho on "sprouts".

Krycho points out that most of our online infrastructure is organized around feeds of posts, which are "published" or "announced" as finished work. But creative work naturally develops in drafts and increments. It might be best to publish at first only the barest outline of a thing, and then collaborate in the open to flesh it out and bring it forward. What we want to announce, then, are not new posts, but a beginning and then new milestones. If we can, we'd want to retain the full history of the process.

I've gone a fair distance towards implementing one version of this vision recently. My blogging infrastructure is my own static-site generation library unstatic, which is built on top of "untemplates". Untemplates are just thin wrappers around Scala functions.

Like lots of static-site generators, I write in files that are mostly markdown, with some metadata in a special header. But in the untemplate header, I literally write Scala code.

Here's a very simple example, from a recent post, "c3p0 and loom":

> val UntemplateAttributes = immutable.Map[String,Any] (
>   "Title"     -> "c3p0 and loom",
>   "PubDate"   -> "2024-03-18T22:20:00-04:00",
>   "Anchor"    -> "c3p0-and-loom"
> )

given PageBase = PageBase.fromPage(input.renderLocation)

(input : MainBlog.EntryInput)[]~()>      ### modify Title/Author/Pubdate above, add markdown or html below!

I write [a lot of open source software](https://github.com/swaldman), but I've only ever really had one "hit".
That makes me pretty sad, actually. I think some of what I've written is pretty great, and it's lonesome to be the
sole user...

For most posts, I just copy and modify this header, and then write markdown text below. But if I want to do anything more fancy, well, I have the full Scala programming language to work with.

In order to realize a vision of "sprouts", I first implemented update histories as a simple List of UpdateRecord objects.

The post prior to this one has already become a particularly sprouty sprout. The post documents experiments with extensions to RSS. I try stuff out, then, as often as not, I untry it. So there's lots of revising. Here's the beginning of that post, for now:

> val updateHistory =
>    UpdateRecord("2024-06-02T00:25:00-04:00",Some("Drop <code>iffy:timestamp</code>. We can just reuse <code>atom:updated</code> for the same work."),Some("199e44561de3fd9e731a335d8b2a655f42d9bc04")) ::
>    UpdateRecord("2024-06-01T21:35:00-04:00",Some("Add initial take on tags related to updates and revisions."),Some("72eaf9fdfebc9e627bff33bbe1102d4d250ad1d0")) ::
>    UpdateRecord("2024-05-25T23:00:00-04:00",Some("Add JS/CSS so that prior revisions are visually distinct from current."),Some("13de0232319ceab2f830591c318089d18cbec78d")) ::
>    UpdateRecord("2024-05-24T00:25:00-04:00",Some("Drop tags <code>iffy:when-updated</code> and <code>iffy:original-guid</code>, bad appraoch to updates."),Some("394986cb8d9c57f567d324e691a44d50102101ce")) ::
>    Nil
>
> val UntemplateAttributes = immutable.Map[String,Any] (
>   "Title"         -> "The 'iffy' XML namespace",
>   "PubDate"       -> "2024-05-13T04:10:00-04:00",
>   "Permalink"     -> "/xml/iffy/index.html",
>   "UpdateHistory" -> updateHistory,
>   "Sprout"        -> true,
>   "Anchor"        -> "iffy-xml-namespace"
> )

given PageBase = PageBase.fromPage(input.renderLocation)

(input : MainBlog.EntryInput)[]~()>      ### modify Title/Author/Pubdate above, add markdown or html below!

I want to do a lot of things with RSS that require
extensions of RSS (as the RSS spec [foresees](https://www.rssboard.org/rss-specification#extendingRss))...

Each UpdateRecord marks a discretionary choice, to declare a "significant" or "material" update. Most updates are not! There are typically several minor revisions, typo fixes, and tweaks, between these noted updates.

When I decide a revision is serious, I provide a timestamp, and, optionally, a description and a "revision spec". The description is self-explanatory. The revision spec is arbitrary. When I define a site, I optionally provide an RevisionBinder that is able to convert a revision spec and a path into the contents of a resource within the referenced revision.

There might be many different implementations of RevisionBinder, each with its own kind of revision specification. The one that exists for now just pulls resources from git commits.

The revision specs shown are just git commits, referenced in full-length hex. With each "major" update, I provide the hex for the commit prior to my update, the one it is superceding. That usually will not be the same commit as the "major" update prior, because most "major" updates are followed by a series of minor tweaks.

So, as I work on an evolving document, I note significant updates by adding records to a list, and I include the list I build in UntemplateAttributes, the standard Map in which I also define Title, PubDate, Author, etc. (The example posts omit Author, because this site has a default author — me! — if that field is left unset.)

The update history converts pretty directly into a user readable history. Let's take a look!

(If you want to see the template that lays out the update history, you can find it here.)

To realize the "sprout" vision, we need more than this. We need some means by which people can follow the evolutions of the document. Ideally, when a "major update" is published, subscribers to the blog should see something about that in their feeds. The RSS feeds we generate include <atom:updated> tags, but as Krycho points out, very few feed readers do anything with that.

You'll note that in addition to the UpdateHistory, we've added to UntemplateAttributes a key called Sprout. When the site generator encounters a mapping of Sprout to true on an untemplate, it generates an additional RSS feed that will track the update history of just this post. For our example post, you can find that feed here

The template that lays out blog entries looks for an update history, and if it is there, checks for prior revisions references, diffs, and the sprouts flag. It prepends to the post a brief note with links, to the prior revision if it's available, to the update history, and to the post-specific RSS feed. Go ahead, check out the beginning of our example post.

Going forward, I think I will add the capability of generating "synthetic" posts when there are new major updates. They'd just be formulaic announcements of the updates. They might never appear on the blog front page. But they would be included in the RSS feed. I'd add metadata to the RSS items for these posts, indicating that they are synthetic and describing them, so that tools like feedletter can make intelligent choices about whether and how subscribers should receive notificatios about these posts.

But that is all still to come!

For now, we have update histories with links to prior revisions and diffs, and dedicated RSS feeds by which dedicated collaborators can stay abreast of our burgeoning sprouts.