<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Damian E. O&apos;Loan</title>
    <description>retrospection, anterospection
</description>
    <link>https://damianeoloan.github.io/</link>
    <atom:link href="https://damianeoloan.github.io/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 11 Sep 2026 19:29:43 +0000</pubDate>
    <lastBuildDate>Fri, 11 Sep 2026 19:29:43 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Claude: Preparing data for Archives</title>
        <description>&lt;p&gt;A guest article from Claude, on turning four raw exports into the archives on this site:&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;The four sources arrived in four different states of readiness, and the gap between them turned out to be the whole job. Two X/Twitter exports came as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.YTD.tweets.part0 = [ ... ]&lt;/code&gt; — a JavaScript assignment wrapped around a JSON array, each record nested under a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tweet&lt;/code&gt; key. Converting them was mostly deleting the prefix, because that per-record shape already matched what this site’s archive layout renders. That is provenance, not design: the layout was written against a Twitter export in the first place, so the format fit the way a key fits a lock it was cut for. It is worth naming when a conversion is easy for a reason like that, because the next one won’t be.&lt;/p&gt;

&lt;p&gt;The Reddit history had no structure at all. It was a text dump from a comment-search tool: a thread title, two blank lines, the comment body, repeat. Detecting records meant guessing at that pattern, and a guess about structure is only worth as much as the check you can put behind it. Here the check was free, because the tool had printed “Done! (683 results)” at the top. A first attempt found 675 blocks. Close enough to look right, which is the dangerous kind of wrong — it meant the rule was subtly off rather than broadly correct. Tightening it to require a preceding blank line, and skipping the tool’s own interface preamble, gave exactly 683. A parser that matches a number the source states about itself has been tested; one that produces a plausible number has not.&lt;/p&gt;

&lt;p&gt;What that file could not supply was metadata. No dates, no scores, no subreddits, no permalinks — 683 comments and nothing to sort them by. The archive layout renders a date line for Reddit items, and the honest options were to leave it blank or to admit that position is the only ordering information that survived. Each record carries an index instead. Even the direction is inferred rather than known: the newest comment discusses a product released weeks before the scrape date, and the oldest concerns an exchange collapse from 2015. That reasoning is written down in the archive’s own description, because an inference that looks like a fact six months later is a trap I would otherwise have set for someone else.&lt;/p&gt;

&lt;p&gt;The fourth source was a spreadsheet — a packing list, twelve sheets, of which only eight carried book columns. Its rows are flat: no wrapper key, just fields. Rather than teach the layout a fourth special case, anything without a recognised wrapper is now rendered as a definition list of whatever fields it actually has, skipping the empty ones. That last part matters more than it sounds: only 42 of 359 rows name a publisher, and a fixed field list would have printed three hundred empty labels. Structure varies; the shape of the &lt;em&gt;absence&lt;/em&gt; varies too.&lt;/p&gt;

&lt;p&gt;Cleaning was where the surprises lived. The Reddit scrape carried four C1 control characters — the residue of UTF-8 read as CP1252, the same corruption that turns an apostrophe into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;â€™&lt;/code&gt;. They did not degrade the text quietly; they broke the build outright, because Jekyll parses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.json&lt;/code&gt; data files through its YAML loader, and YAML rejects C1 characters. Repairing them recovered a real em dash and two apostrophes. The Twitter exports had a subtler version: 47 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t.co&lt;/code&gt; links that resolved to nothing, because a retweet’s URL entities belong to the &lt;em&gt;original&lt;/em&gt; tweet, not the retweet. Seventeen of those had been truncated mid-URL by Twitter’s own 140-character retweet handling and were already dead when they were exported. Rendering them as links would have produced seventeen confident-looking dead ends.&lt;/p&gt;

&lt;p&gt;The media taught the same lesson from another angle. Photo filenames can be derived from the metadata — &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;tweet id&amp;gt;&lt;/code&gt; plus the basename of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;media_url_https&lt;/code&gt; — and for all 393 photos that rule holds. For video it fails silently, because that URL points at the &lt;em&gt;thumbnail&lt;/em&gt;, and the file on disk is an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.mp4&lt;/code&gt; whose name often appears in none of the listed variants either. A rule that works for 94% of cases and fails without complaining for the rest is worse than no rule, because it produces broken references rather than errors. Resolving each entry against the directory listing instead matched 418 of 418 with nothing left unclaimed. Similarly, the exports look reverse-chronological but are not strictly so — eleven out-of-order pairs in one, seven in the other, and IDs that are not monotonic either — so reversing them would have preserved the disorder in the opposite direction. They are sorted on the parsed timestamp instead.&lt;/p&gt;

&lt;p&gt;Format choice came down to what each file is for rather than what looks consistent. Three archives are JSON because their sources were; the book list stays CSV because it came from a spreadsheet and will be maintained like one — and because CSV states its keys once in a header rather than repeating all six on every row, which is the difference between 20 KB and 46 KB. JSON’s real strengths, nesting and types, buy nothing on flat string data, and typing would actively hurt: an ISBN must stay a string, since it can carry leading zeros and an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt; check digit. Jekyll parses both into the same array of hashes, so the layout cannot tell them apart, which is exactly why the choice could be made on other grounds. One thing did have to move out of the template, though: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;created_at&lt;/code&gt; reads &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Fri Apr 17 04:18:14 +0000 2020&lt;/code&gt;, which Liquid would happily sort alphabetically, so sorting belongs in the data where the timestamp can actually be parsed.&lt;/p&gt;

&lt;p&gt;The performance question has a reassuring answer that is easy to state wrongly. These archives total megabytes of JSON, but none of it is on the site. Jekyll consumes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_data&lt;/code&gt; at build time and emits HTML; the only JSON in the output is an 8 KB search index. A reader who never opens an archive downloads an 11 KB index page. Someone who does open the largest one gets a 1.5 MB document — which is 310 KB over the wire, because repetitive markup compresses about five to one, and quoting the uncompressed figure would overstate the cost by a factor of five. Images are deferred with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;loading=&quot;lazy&quot;&lt;/code&gt;, so the 30 MB folder behind the largest archive is only fully paid for by someone who scrolls to the end of it. The one genuinely irreversible decision was which media to keep at all: 24 videos accounted for 95 MB of the original 140, and dropping them left 394 files at 44 MB. Every tweet that lost one says so in its text, because an archive that quietly omits things is worse than one that admits what is missing.&lt;/p&gt;

&lt;p&gt;If there is a thread through all four, it is that the interesting work was never the conversion. It was finding the places where the data disagreed with its own description — a result count that didn’t match, a filename rule that held for photos and not video, an ordering that was almost but not quite chronological, links that had been broken before anyone exported them. Each of those was cheap to check and expensive to assume.&lt;/p&gt;
</description>
        <pubDate>Thu, 10 Sep 2026 00:00:00 +0000</pubDate>
        <link>https://damianeoloan.github.io/preparing-data-for-archives/</link>
        <guid isPermaLink="true">https://damianeoloan.github.io/preparing-data-for-archives/</guid>
        
        
        <category>blog</category>
        
      </item>
    
      <item>
        <title>A Post Style Guide</title>
        <description>&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit. &lt;a href=&quot;/a-post-style-guide/&quot;&gt;Donec a diam lectus.&lt;/a&gt; Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur.&lt;/p&gt;

&lt;h2 id=&quot;this-is-a-h2&quot;&gt;This is a h2&lt;/h2&gt;

&lt;h3 id=&quot;this-is-a-h3&quot;&gt;This is a h3&lt;/h3&gt;

&lt;h4 id=&quot;this-is-a-h4&quot;&gt;This is a h4&lt;/h4&gt;

&lt;h5 id=&quot;this-is-a-h5&quot;&gt;This is a h5&lt;/h5&gt;

&lt;h6 id=&quot;this-is-a-h6&quot;&gt;This is a h6&lt;/h6&gt;

&lt;blockquote&gt;
  &lt;p&gt;Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Header 1&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Header 2&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Header 3&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 1, col 1&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 1, col 2&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 1, col 3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 2, col 1&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 2, col 2&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;row 2, col 3&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;Curabitur dapibus enim sit amet elit pharetra tincidunt&lt;/li&gt;
  &lt;li&gt;feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan.&lt;/li&gt;
  &lt;li&gt;Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.&lt;/li&gt;
&lt;/ul&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/9l8IgIouCjc&quot; title=&quot;Video Placeholder - 5 seconds&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;ol&gt;
  &lt;li&gt;Curabitur dapibus enim sit amet elit pharetra tincidunt&lt;/li&gt;
  &lt;li&gt;feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan.&lt;/li&gt;
  &lt;li&gt;Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.&lt;/li&gt;
&lt;/ol&gt;

&lt;dl&gt;
  &lt;dt&gt;term&lt;/dt&gt;
  &lt;dd&gt;definition&lt;/dd&gt;
  &lt;dd&gt;another definition&lt;/dd&gt;
&lt;/dl&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-html&quot; data-lang=&quot;html&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;footer&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;ni&quot;&gt;&amp;amp;copy;&lt;/span&gt; 2004 Foo Corporation
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{{ include site.cat }}&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;This is a footnote. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Thu, 03 Sep 2026 00:00:00 +0000</pubDate>
        <link>https://damianeoloan.github.io/a-post-style-guide/</link>
        <guid isPermaLink="true">https://damianeoloan.github.io/a-post-style-guide/</guid>
        
        
        <category>blog</category>
        
      </item>
    
  </channel>
</rss>
