<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged publishing</title>
<link>https://coderlegion.com/tag/publishing</link>
<description>Powered by Question2Answer</description>
<item>
<title>Design Publishing Queues That Survive Image Provider Outages</title>
<link>https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</link>
<description>&lt;p&gt;A publishing queue should not forget an article because a photo API had a bad afternoon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;if (providerError.isTransient) {
  return queue.defer({ state: &quot;MEDIA_PENDING&quot;, retryAfter: &quot;1h&quot; });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The retry design must respect &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Cloudflare Workflow limits&lt;/a&gt; and the provider's &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;Pexels API documentation&lt;/a&gt;; provider failure does not decide whether the article itself is valid.&lt;/p&gt;
&lt;p&gt;Yet that is exactly what happens when a pipeline treats every missing hero image as &quot;invalid content.&quot;&lt;/p&gt;
&lt;p&gt;The article may be complete. The metadata may be valid. The internal links may pass. The only failure is an external media provider returning no usable result, a rate-limit response, or a temporary server error.&lt;/p&gt;
&lt;p&gt;That is not the same class of failure as a malformed article.&lt;/p&gt;
&lt;p&gt;The queue should preserve the article and defer the &lt;strong&gt;media stage&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;separate-source-validity-from-media-availability&quot;&gt;Separate source validity from media availability&lt;/h3&gt;
&lt;p&gt;A clean pipeline asks two different questions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Is the article publishable?
Is a valid hero asset available right now?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first question is editorial.&lt;/p&gt;
&lt;p&gt;The second depends partly on external systems.&lt;/p&gt;
&lt;p&gt;If you merge them into one boolean, this happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;article valid
photo provider unavailable
=&amp;gt; source rejected
=&amp;gt; next topic generated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now a temporary outage has changed editorial history.&lt;/p&gt;
&lt;p&gt;A better state model is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;VALIDATED
MEDIA_PENDING
MEDIA_SELECTED
READY_TO_PUBLISH
PUBLISHED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The article identity stays the same while the media stage retries.&lt;/p&gt;
&lt;h3 id=&quot;classify-media-failures&quot;&gt;Classify media failures&lt;/h3&gt;
&lt;p&gt;Not every visual failure deserves a retry.&lt;/p&gt;
&lt;h4 id=&quot;retryable&quot;&gt;Retryable&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;provider 429 rate limit;&lt;/li&gt;
&lt;li&gt;temporary 5xx response;&lt;/li&gt;
&lt;li&gt;network timeout;&lt;/li&gt;
&lt;li&gt;provider returns zero acceptable results for one query;&lt;/li&gt;
&lt;li&gt;asset download temporarily fails;&lt;/li&gt;
&lt;li&gt;deployment check is still pending.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;deterministic-repair&quot;&gt;Deterministic repair&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;selected asset already used when uniqueness is required;&lt;/li&gt;
&lt;li&gt;wrong dimensions;&lt;/li&gt;
&lt;li&gt;missing attribution;&lt;/li&gt;
&lt;li&gt;stale manifest hash;&lt;/li&gt;
&lt;li&gt;unsupported media type.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;terminal-for-that-candidate&quot;&gt;Terminal for that candidate&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;license does not allow the intended use;&lt;/li&gt;
&lt;li&gt;source URL cannot be established;&lt;/li&gt;
&lt;li&gt;raster is corrupt;&lt;/li&gt;
&lt;li&gt;asset content is clearly irrelevant;&lt;/li&gt;
&lt;li&gt;provider explicitly rejects the request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A terminal candidate is not necessarily a terminal article. Select another asset.&lt;/p&gt;
&lt;h3 id=&quot;bound-provider-retries&quot;&gt;Bound provider retries&lt;/h3&gt;
&lt;p&gt;External APIs need retry budgets.&lt;/p&gt;
&lt;p&gt;Pexels’ &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.pexels.com/api/documentation/&quot;&gt;API documentation&lt;/a&gt; currently documents default API limits of 200 requests per hour and 20,000 per month. A publisher that retries without bounds can turn temporary scarcity into a quota problem. Cloudflare’s &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/reference/limits/&quot;&gt;Workflow limits&lt;/a&gt; are a separate runtime ceiling, so keep provider quotas and orchestration limits distinct.&lt;/p&gt;
&lt;p&gt;Use a fixed strategy such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;query 1 -&amp;gt; retry transient failure up to 2 times
query 2 -&amp;gt; try one alternate search phrase
reviewed catalog -&amp;gt; use only if a genuinely relevant asset exists
otherwise -&amp;gt; MEDIA_PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not keep changing the query forever inside one Workflow instance.&lt;/p&gt;
&lt;p&gt;The purpose of retries is to absorb transient noise, not to search the entire internet before the run can finish.&lt;/p&gt;
&lt;h3 id=&quot;keep-the-source-queued&quot;&gt;Keep the source queued&lt;/h3&gt;
&lt;p&gt;When media is unavailable, persist the article in a state that makes the next run resume it.&lt;/p&gt;
&lt;p&gt;Do not delete the source, mark it duplicate, move to a new topic, create another draft with the same intent, or reset the article ID.&lt;/p&gt;
&lt;p&gt;A durable record needs enough identity to resume:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;article_id: art_example
slug: example-article
state: MEDIA_PENDING
revision: 2
visual_attempts: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether this lives in Git, Workflow state, or another durable store depends on the architecture. The invariant is the important part: the article survives.&lt;/p&gt;
&lt;h3 id=&quot;reuse-reviewed-assets-carefully&quot;&gt;Reuse reviewed assets carefully&lt;/h3&gt;
&lt;p&gt;A local catalog can reduce provider dependence.&lt;/p&gt;
&lt;p&gt;If the catalog already contains an unused or reusable raster that is genuinely relevant and its license permits reuse, it may be a better fallback than another external request.&lt;/p&gt;
&lt;p&gt;But &quot;available&quot; is not enough.&lt;/p&gt;
&lt;p&gt;Check topic relevance, provider/source URL, license, attribution, byte hash, dimensions, reuse rules, and whether the same visual is already overused.&lt;/p&gt;
&lt;p&gt;Never reuse an unrelated image simply to make the pipeline green.&lt;/p&gt;
&lt;h3 id=&quot;bind-provenance-when-selection-succeeds&quot;&gt;Bind provenance when selection succeeds&lt;/h3&gt;
&lt;p&gt;Once an asset is selected, record the evidence immediately.&lt;/p&gt;
&lt;p&gt;Useful fields include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;provider: Pexels
source_url: https://www.pexels.com/photo/...
license_url: https://www.pexels.com/license/
attribution: Photo by ...
article_id: art_example
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After downloading the final raster, add the MIME type, width, height, byte count, and SHA-256 of the transformed file.&lt;/p&gt;
&lt;p&gt;The first block explains where the image came from. The second explains exactly which bytes the publication uses.&lt;/p&gt;
&lt;p&gt;Do not invent file metadata before the file exists.&lt;/p&gt;
&lt;h3 id=&quot;avoid-duplicate-work-after-uncertain-responses&quot;&gt;Avoid duplicate work after uncertain responses&lt;/h3&gt;
&lt;p&gt;Media providers and storage APIs can fail after doing the work.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;download completes
network layer times out before caller records success
retry starts
same image downloaded again under another asset ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use stable stage identity.&lt;/p&gt;
&lt;p&gt;If an asset selection has already been recorded for article A revision 3, the retry should inspect that record before searching again.&lt;/p&gt;
&lt;p&gt;Likewise, if the final raster already exists and its hash matches the intended manifest, verify it instead of creating another copy.&lt;/p&gt;
&lt;p&gt;This is the same idempotency principle used throughout &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/building-a-reliable-batch-publishing-workflow/&quot;&gt;Building a Reliable Batch Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;verify-the-actual-raster-not-provider-metadata&quot;&gt;Verify the actual raster, not provider metadata&lt;/h3&gt;
&lt;p&gt;A provider response can describe the original photograph, while your pipeline may crop or resize it.&lt;/p&gt;
&lt;p&gt;The publication should bind to the final transformed bytes, not merely the provider metadata.&lt;/p&gt;
&lt;p&gt;After transformation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;decode the file;&lt;/li&gt;
&lt;li&gt;read actual dimensions;&lt;/li&gt;
&lt;li&gt;compute byte length;&lt;/li&gt;
&lt;li&gt;compute SHA-256;&lt;/li&gt;
&lt;li&gt;update the asset manifest;&lt;/li&gt;
&lt;li&gt;verify the article points to that asset.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The provider page remains provenance. The transformed file becomes publication evidence.&lt;/p&gt;
&lt;h3 id=&quot;keep-provider-outages-out-of-editorial-scoring&quot;&gt;Keep provider outages out of editorial scoring&lt;/h3&gt;
&lt;p&gt;A photo API outage says nothing about whether an article has information gain.&lt;/p&gt;
&lt;p&gt;Do not lower article quality, factual confidence, duplicate status, or rank-potential merely because an image API is unavailable.&lt;/p&gt;
&lt;p&gt;Instead report the failure in the stage that owns it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;editorial: PASS
research: PASS
visual: DEFERRED_PROVIDER
publication: PENDING
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the blocker understandable to both humans and automation.&lt;/p&gt;
&lt;h3 id=&quot;verify-after-deployment&quot;&gt;Verify after deployment&lt;/h3&gt;
&lt;p&gt;A queue is not finished when the image reaches Git.&lt;/p&gt;
&lt;p&gt;After deployment, fetch the public hero URL and verify a successful response, correct content type, decodable bytes, expected dimensions, and that the expected article actually references it.&lt;/p&gt;
&lt;p&gt;If the article is live but the asset is stale, keep the same publication pending until the intended raster is visible.&lt;/p&gt;
&lt;p&gt;That exact-version discipline is part of &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/&quot;&gt;Designing a Reliable Serverless AI Publishing Workflow&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;design-for-a-provider-independent-future&quot;&gt;Design for a provider-independent future&lt;/h3&gt;
&lt;p&gt;Do not let one provider's response schema leak across the whole publishing system.&lt;/p&gt;
&lt;p&gt;Normalize a candidate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type ImageCandidate = {
  provider: string;
  sourceUrl: string;
  downloadUrl: string;
  attribution: string;
  licenseUrl: string;
  width?: number;
  height?: number;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the rest of the pipeline can perform relevance checks, dedupe, transforms, manifest binding, and deployment verification without caring where the candidate came from.&lt;/p&gt;
&lt;p&gt;That makes adding a second provider a local adapter change instead of a workflow rewrite.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-recovery-algorithm&quot;&gt;A practical recovery algorithm&lt;/h3&gt;
&lt;p&gt;When an article reaches the visual stage:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1. inspect existing bound asset
2. if healthy -&amp;gt; continue
3. inspect reviewed catalog for relevant candidate
4. if found -&amp;gt; bind, transform, verify
5. otherwise query provider within request budget
6. reject duplicates or invalid provenance
7. transform final raster
8. validate bytes and dimensions
9. if provider unavailable -&amp;gt; MEDIA_PENDING
10. retry same article later
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The crucial line is number 10.&lt;/p&gt;
&lt;p&gt;When maintaining these pipelines, teams should also review policies for &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/preventing-search-intent-duplicates-in-automated-publishing/&quot;&gt;preventing search intent&lt;/a&gt; duplicates in automated systems to ensure safe retries.&lt;/p&gt;
&lt;h3 id=&quot;verification-and-execution-checklist&quot;&gt;Verification and execution checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Inspect the active article state to confirm &lt;code&gt;MEDIA_PENDING&lt;/code&gt; is recorded instead of deleting the draft.&lt;/li&gt;
&lt;li&gt;Verify that external API calls respect rate-limit thresholds and orchestration timeouts.&lt;/li&gt;
&lt;li&gt;Confirm that transformed asset hashes match the recorded manifest before deployment.&lt;/li&gt;
&lt;li&gt;Check public staging endpoints to ensure the final raster is accessible and correctly linked.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31607/design-publishing-queues-that-survive-image-provider-outages</guid>
<pubDate>Sun, 18 Oct 2026 11:52:14 +0000</pubDate>
</item>
<item>
<title>Preserve Captions in Markdown Publishing</title>
<link>https://coderlegion.com/31517/preserve-captions-in-markdown-publishing</link>
<description>&lt;p&gt;Markdown looks simple because it is easy to type. That simplicity can be deceptive when you build an automated publisher.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def extract_hero_and_caption(ast_nodes):
    hero_index = find_hero_image_node(ast_nodes)
    if hero_index is not None and is_valid_caption_block(ast_nodes[hero_index + 1]):
        return ast_nodes[hero_index + 2:]
    return ast_nodes[hero_index + 1:]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://spec.commonmark.org/0.31.2/&quot;&gt;CommonMark 0.31.2&lt;/a&gt; defines link and image syntax; caption handling is an application-level convention that the transform must preserve explicitly.&lt;/p&gt;
&lt;p&gt;A regular expression removes a hero image. Another inserts an internal link. A third strips a caption. The pipeline passes &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/how-to-build-robust-viewmodel-unit-tests/&quot;&gt;unit tests&lt;/a&gt;, but the rendered article suddenly contains an orphan caption or an unnatural sentence where one word has been replaced by an entire destination title.&lt;/p&gt;
&lt;p&gt;The problem is not Markdown itself. The problem is treating structured editorial text as an unstructured string.&lt;/p&gt;
&lt;p&gt;Two transformations deserve particular care:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;extracting an image and its visible caption;&lt;/li&gt;
&lt;li&gt;inserting contextual links without rewriting the author's voice.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;standard-markdown-does-not-give-you-a-universal-caption-node&quot;&gt;Standard Markdown does not give you a universal caption node&lt;/h3&gt;
&lt;p&gt;CommonMark defines links and images, but there is no universal native image-plus-visible-caption construct.&lt;/p&gt;
&lt;p&gt;Publishers therefore use conventions such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![Architecture overview](hero.jpg)

*The pipeline keeps content and deployment state separate.*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or raw HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;`&amp;lt;figure&amp;gt;`
  `&amp;lt;img src=&quot;hero.jpg&quot; alt=&quot;Architecture overview&quot;&amp;gt;`
  `&amp;lt;figcaption&amp;gt;`The pipeline keeps content and deployment state separate.`&amp;lt;/figcaption&amp;gt;`
`&amp;lt;/figure&amp;gt;`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first form is just an image block followed by a paragraph. Your pipeline may decide that the paragraph is a caption, but that interpretation is application-specific.&lt;/p&gt;
&lt;p&gt;That means caption extraction needs an explicit rule.&lt;/p&gt;
&lt;h3 id=&quot;the-greedy-whitespace-bug&quot;&gt;The greedy whitespace bug&lt;/h3&gt;
&lt;p&gt;Consider this input:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;![A deployment workflow](hero.jpg)

*The publication path has three independent gates.*

The article begins here with a normal paragraph.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A regex designed to remove the image may accidentally consume too much or too little whitespace.&lt;/p&gt;
&lt;p&gt;If it removes only the image token, the caption remains at the top of the article as ordinary prose.&lt;/p&gt;
&lt;p&gt;If it greedily absorbs multiple following paragraphs, the first real paragraph disappears too.&lt;/p&gt;
&lt;p&gt;The dangerous pattern is matching the image, then matching arbitrary whitespace and text.&lt;/p&gt;
&lt;p&gt;The safer rule is structural:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;locate the image block;&lt;/li&gt;
&lt;li&gt;identify exactly the next block;&lt;/li&gt;
&lt;li&gt;classify that block as a caption only if it matches your caption convention;&lt;/li&gt;
&lt;li&gt;remove those exact blocks;&lt;/li&gt;
&lt;li&gt;preserve all other whitespace and prose.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;prefer-an-ast-when-transforms-become-important&quot;&gt;Prefer an AST when transforms become important&lt;/h3&gt;
&lt;p&gt;For one tightly controlled format, a carefully tested parser can be enough.&lt;/p&gt;
&lt;p&gt;As soon as your pipeline performs several Markdown transformations, an abstract syntax tree becomes easier to reason about than stacked regular expressions.&lt;/p&gt;
&lt;p&gt;The conceptual flow becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Markdown
  -&amp;gt; parse blocks/nodes
  -&amp;gt; identify hero image node
  -&amp;gt; inspect immediate next paragraph
  -&amp;gt; classify caption
  -&amp;gt; transform nodes
  -&amp;gt; render Markdown or HTML
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the next paragraph is an actual node, not a substring discovered by a greedy expression.&lt;/p&gt;
&lt;p&gt;An AST also helps distinguish links inside paragraphs, headings, code fences, image alt text, and existing link nodes.&lt;/p&gt;
&lt;p&gt;That matters for internal-link insertion.&lt;/p&gt;
&lt;h3 id=&quot;contextual-links-should-preserve-an-existing-phrase&quot;&gt;Contextual links should preserve an existing phrase&lt;/h3&gt;
&lt;p&gt;Imagine the article contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A reliable serverless publishing workflow needs explicit retry boundaries.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You want to link &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/&quot;&gt;serverless publishing workflow&lt;/a&gt; to a relevant internal article.&lt;/p&gt;
&lt;p&gt;The good result is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;A reliable [serverless publishing workflow](https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/) needs explicit retry boundaries.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A bad automation finds only the word workflow and replaces it with the destination page title:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;A reliable serverless publishing [Designing a Reliable Serverless AI Publishing Workflow](https://raylabs.app/articles/designing-a-reliable-serverless-ai-publishing-workflow/) needs explicit retry boundaries.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The URL may be correct, but the sentence no longer sounds authored.&lt;/p&gt;
&lt;p&gt;The rule should be simple: link an existing natural phrase, and do not rewrite prose to manufacture an anchor.&lt;/p&gt;
&lt;h3 id=&quot;score-candidate-phrases-instead-of-replacing-tokens&quot;&gt;Score candidate phrases instead of replacing tokens&lt;/h3&gt;
&lt;p&gt;A practical link inserter can work from phrase candidates.&lt;/p&gt;
&lt;p&gt;For each destination:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;collect phrases already present in the article;&lt;/li&gt;
&lt;li&gt;reject phrases inside headings, code, existing links, and captions;&lt;/li&gt;
&lt;li&gt;prefer multiword phrases with clear semantic overlap;&lt;/li&gt;
&lt;li&gt;choose the first contextually useful occurrence;&lt;/li&gt;
&lt;li&gt;wrap exactly that phrase;&lt;/li&gt;
&lt;li&gt;do not alter surrounding words.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A basic score can favor three-to-six-word phrases, exact topic terms, and body paragraphs over navigation-like text.&lt;/p&gt;
&lt;p&gt;The scoring system does not need artificial intelligence. It needs predictable editorial constraints.&lt;/p&gt;
&lt;h3 id=&quot;never-link-inside-code-image-syntax-or-an-existing-link&quot;&gt;Never link inside code, image syntax, or an existing link&lt;/h3&gt;
&lt;p&gt;String replacement is especially risky because the same phrase may appear in code, image alt text, headings, or a link that already exists.&lt;/p&gt;
&lt;p&gt;A syntax-aware transform can exclude those node types.&lt;/p&gt;
&lt;p&gt;A regex stack tends to solve this by adding exceptions until maintenance becomes harder than parsing Markdown correctly.&lt;/p&gt;
&lt;h3 id=&quot;link-insertion-should-be-idempotent&quot;&gt;Link insertion should be idempotent&lt;/h3&gt;
&lt;p&gt;Run your publishing transform twice.&lt;/p&gt;
&lt;p&gt;The second run should not:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;add a second copy of the same link;&lt;/li&gt;
&lt;li&gt;nest Markdown links;&lt;/li&gt;
&lt;li&gt;change the anchor phrase;&lt;/li&gt;
&lt;li&gt;reinsert a removed caption;&lt;/li&gt;
&lt;li&gt;move the hero;&lt;/li&gt;
&lt;li&gt;change unrelated whitespace.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Idempotency is one of the strongest tests for editorial automation because production jobs are retried.&lt;/p&gt;
&lt;p&gt;If the same draft can be transformed twice with a different output, recovery becomes dangerous.&lt;/p&gt;
&lt;p&gt;This is part of the same design problem discussed in &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/automating-medium-draft-workflows/&quot;&gt;Automating Medium Draft Workflows&lt;/a&gt;: retries should continue the same artifact rather than mutate it unpredictably.&lt;/p&gt;
&lt;h3 id=&quot;separate-editorial-transformation-from-privacy-validation&quot;&gt;Separate editorial transformation from privacy validation&lt;/h3&gt;
&lt;p&gt;A Markdown transformer may also need to reject private repository URLs or internal hostnames.&lt;/p&gt;
&lt;p&gt;Keep that as a separate validator.&lt;/p&gt;
&lt;p&gt;Caption extraction answers which blocks belong to the hero presentation. Internal-link insertion answers which existing phrase can safely become an anchor. Privacy validation answers whether any forbidden URL or identifier remains in the final document.&lt;/p&gt;
&lt;p&gt;RayLabs treats private-link detection as its own boundary in &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/preventing-private-repository-links-in-public-articles/&quot;&gt;Preventing Private Repository Links in Public Articles&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;verify-the-rendered-result-not-only-the-source&quot;&gt;Verify the rendered result, not only the source&lt;/h3&gt;
&lt;p&gt;A Markdown file can look correct and still render incorrectly under the target parser.&lt;/p&gt;
&lt;p&gt;Your final test should render the transformed document through the same stack used in production and inspect the result.&lt;/p&gt;
&lt;p&gt;For caption handling, confirm that exactly one hero exists, the caption appears in the intended location, and the caption does not appear again as the first article paragraph.&lt;/p&gt;
&lt;p&gt;For contextual links, verify that anchor text matches the intended phrase, the destination is the expected existing URL, no nested anchors exist, and code and headings remain unchanged.&lt;/p&gt;
&lt;h3 id=&quot;execution-checklist&quot;&gt;Execution Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Confirm caption extraction isolates hero image and caption blocks without consuming subsequent paragraphs.&lt;/li&gt;
&lt;li&gt;[ ] Verify contextual link insertion wraps exact natural phrases without altering surrounding text.&lt;/li&gt;
&lt;li&gt;[ ] Execute the publishing transform twice to guarantee idempotency.&lt;/li&gt;
&lt;li&gt;[ ] Render the final output through the production parser to validate HTML structure.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31517/preserve-captions-in-markdown-publishing</guid>
<pubDate>Sat, 17 Oct 2026 15:04:18 +0000</pubDate>
</item>
<item>
<title>Bristol Replaced Its Voynich Press Release. Only the Withdrawn Page Tells You What Happened.</title>
<link>https://coderlegion.com/26180/bristol-replaced-its-voynich-press-release-only-the-withdrawn-page-tells-you-what-happened</link>
<description>&lt;p&gt;On 15 May 2019 the University of Bristol's news site published a story under the headline &quot;Bristol academic cracks Voynich code, solving century-old mystery of medieval text.&quot; Open the same address today and you get a different page with a different headline: &quot;Statement re: Voynich research paper,&quot; dated 16 May 2019. The story is not there. In its place the university explains that &quot;concerns have been raised about the validity of this research from academics in the fields of linguistics and medieval studies,&quot; that it takes such concerns seriously, and that it has &quot;removed the story regarding this research from our website to seek further validation and allow further discussions both internally and with the journal concerned.&quot;&lt;/p&gt;
&lt;p&gt;That page is the subject of this piece. Not the manuscript, and not whether it was deciphered. It was not, and the people qualified to say so said it within a day. The subject is what a reader who arrives in 2026 can learn from each of the things the claim left behind, because it left behind at least three, they have three different owners, and seven years later they do not agree.&lt;/p&gt;
&lt;h3 id=&quot;the-three-artifacts-and-who-owns-them&quot;&gt;The three artifacts and who owns them&lt;/h3&gt;
&lt;p&gt;A public research claim deposits a paper, a press release, and, if things go wrong, a correction. Each is kept by a different institution under different rules, and nobody is obliged to reconcile them.&lt;/p&gt;
&lt;p&gt;The paper is &quot;The Language and Writing System of MS408 (Voynich) Explained,&quot; by Gerard Cheshire, in &lt;em&gt;Romance Studies&lt;/em&gt;, volume 37, issue 1, pages 30 to 67, published online on 29 April 2019. Its abstract lists nine features of the manuscript and then says: &quot;As a result, identifying the language and solving the writing system required some ingenuity and lateral thinking, but both were duly revealed&quot; (accepted manuscript, p. 2). The body states that &quot;the manuscript was compiled by a Dominican nun as a source of reference for the female royal court to which her monastery was affiliated&quot; (p. 3), that it &quot;originates from Castello Aragonese, an island castle and citadel off Ischia, and was compiled for Maria of Castile, Queen of Aragon&quot; (p. 4), and that the language &quot;is known as proto-Romance (prototype-Romance)&quot; (p. 4). The page numbers are those of the accepted author manuscript, which the University of Bristol's own research portal hosts as a PDF, because the publisher's page refused to be read by the tools used for this piece. That detail matters later.&lt;/p&gt;
&lt;p&gt;The press release was the paper with a clock attached. Bristol's copy is gone, but the wire copies are not, and they agree with each other. Its first sentence read: &quot;A University of Bristol academic has succeeded where countless cryptographers, linguistics scholars and computer programs have failed - by cracking the code of the 'world's most mysterious text', the Voynich manuscript.&quot; Its fourth paragraph supplied the clock: &quot;It took Research Associate Dr. Gerard Cheshire two weeks, using a combination of lateral thinking and ingenuity, to identify the language and writing system.&quot; Put the abstract beside that sentence and you can see the release being written: the paper's &quot;ingenuity and lateral thinking&quot; becomes the release's &quot;lateral thinking and ingenuity,&quot; and two weeks is added.&lt;/p&gt;
&lt;p&gt;The correction is the statement that now occupies the release's address. It does three things and it is worth reading them as three separate acts. It records that concerns were raised. It removes the story. And it disclaims the affiliation: the research &quot;was entirely the author's own work and is not affiliated with the University of Bristol, the Faculty of Arts or the Centre for Medieval Studies.&quot; What it does not do is say the research is wrong. The university withdrew its endorsement and its name. It did not, and could not, withdraw the paper.&lt;/p&gt;
&lt;h3 id=&quot;the-day-it-happened&quot;&gt;The day it happened&lt;/h3&gt;
&lt;p&gt;The sequence is short. The release went out on 15 May. By that evening the objections were public. Gizmodo's corrective of 17 May quoted the medievalist Lisa Fagin Davis, executive director of the Medieval Academy of America, on the method: &quot;I tried several years ago to reproduce Cheshire's Voynich results, because initially I was intrigued. But when you apply his Roman-letter substitutions and then try to translate the result, you have no choice but to be subjective. It's gibberish. The methodology falls apart.&quot; The same article quoted Greg Kondrak of the University of Alberta, whose objection is about agreement rather than taste: &quot;a number of people have come up with a mapping to Latin letters, but those mappings rarely agree with each other, or with this proposal.&quot; A Language Log commenter put the linguistic point in one sentence: a proto-language &quot;is most emphatically not a random collection of words from its daughter languages.&quot;&lt;/p&gt;
&lt;p&gt;Michael Stillman's account for Rare Book Hub gives the timing of the withdrawal in one line: &quot;The following day, after the report hit the news wires, the University of Bristol withdrew its news release.&quot; So the correction arrived about a day after the release. Everything since has been the three artifacts drifting apart.&lt;/p&gt;
&lt;h3 id=&quot;what-did-not-happen-the-paper-is-exactly-where-it-was&quot;&gt;What did not happen: the paper is exactly where it was&lt;/h3&gt;
&lt;p&gt;The sentence this piece was most careful about is the one it cannot find a way to write. The paper was not retracted.&lt;/p&gt;
&lt;p&gt;That is checkable without reading anything. Crossref, the registry behind the DOI, records post-publication notices as relations on the original record: a retraction, a correction, or an expression of concern appears as an &quot;updated-by&quot; entry. On 3 September 2026 the record for 10.1080/02639904.2019.1599566 carried none. The title, journal, volume, issue, pages and dates are all present; the update fields are empty. The query that shows this is a dozen lines of Python and is shipped beside this essay, so anyone can re-run it and see whether that is still true on the day they read this.&lt;/p&gt;
&lt;p&gt;What the journal said at the time is a separate question, and it should not be blurred into that one. The coverage opened for this piece, and the DOI record, contain no statement from &lt;em&gt;Romance Studies&lt;/em&gt; or its publisher, Taylor &amp;amp; Francis. That is the absence of my finding one. It is not evidence that nothing was said, and this piece does not build on the journal having stayed silent. It builds on something narrower and firmer: whatever anyone said in May 2019, the peer-reviewed record itself carries no marker, and it is the record a search engine, a citation manager, or a curious reader will reach first.&lt;/p&gt;
&lt;p&gt;There is a fourth artifact, and it is the strangest. The University of Bristol's research portal still hosts the accepted manuscript of the paper as a downloadable PDF, with a cover sheet generated on 21 July 2026, and lists the author's affiliations as the School of Biological Sciences and the Bristol Doctoral College. The last page of that manuscript carries a symbol key signed &quot;Gerard Edward Cheshire. University of Bristol&quot; (p. 48). No note, no link to the statement, nothing. So one Bristol page says the research is not affiliated with the university, and another Bristol page serves the research under the university's name. Both are doing their jobs. The news office corrects its news. The repository preserves what its researchers deposit. Neither is wrong by its own rules, and a reader who finds just one of them will come away with a picture the other would spoil.&lt;/p&gt;
&lt;h3 id=&quot;the-coverage-is-not-one-artifact-it-is-a-population&quot;&gt;The coverage is not one artifact, it is a population&lt;/h3&gt;
&lt;p&gt;It is tempting to say the coverage is the third owner, alongside the university and the journal. It is not one thing. It is a population of pages, and on 3 September 2026 they were in every possible state at once.&lt;/p&gt;
&lt;p&gt;Sci-News published its story on 15 May 2019 and it still opens the way it opened that morning: the manuscript &quot;may be written in proto-Romance,&quot; compiled &quot;by a Dominican nun as a source of reference for Maria of Castile, Queen of Aragon, according to research by University of Bristol's Dr. Gerard Cheshire.&quot; There is no update, no editor's note, no mention that the release was withdrawn. Popular Archaeology carries the release itself, headline and all, with the two-weeks sentence intact and no note either. A reader who lands on either page is standing in 15 May 2019 and has no way to know it.&lt;/p&gt;
&lt;p&gt;New Atlas's story of 16 May, by James Holloway, now lives at a different domain, and it did what the others did not: an editor's note at the foot of the article quotes the university's statement in full. Language Log's post of the same day, by Victor Mair, reproduced the release's opening sentence and then, later, appended the Guardian's headline, &quot;University backtracks on disputed Voynich manuscript theory.&quot; Gizmodo's piece two days later was the correction from the start.&lt;/p&gt;
&lt;p&gt;So the coverage falls into three kinds by chance of editorial habit: pages frozen at the claim, pages that grew a note, and pages written after the collapse. Nothing on any of them tells a reader which kind they are holding. The only way to know is to already know, which is the one thing a reader arriving from a search does not.&lt;/p&gt;
&lt;h3 id=&quot;the-standard-none-of-these-three-owners-met-which-is-why-this-keeps-happening&quot;&gt;The standard none of these three owners met, which is why this keeps happening&lt;/h3&gt;
&lt;p&gt;The reason the specialists could answer within a day is that the test has not changed in a century. A proposed reading of the manuscript is judged by whether it produces coherent text from the whole of it, not a plausible gloss on a few chosen words. Jillian Foley's 2020 survey for Undark traces the pattern back to 1921, when William Newbold, a philosopher at the University of Pennsylvania, claimed the manuscript was a thirteenth-century scientific treatise, and she states the running total plainly: &quot;every claim of a Voynich solution, including both of last year's, has been either ignored or debunked by other experts.&quot; Davis's objection above is the same test applied to one method. Apply the substitutions, translate the result, and see whether you had to choose.&lt;/p&gt;
&lt;p&gt;A stable, public test does not stop the claims arriving. It makes them cheap to answer. That asymmetry is the engine of this story: the claim took a paper, a release and a news cycle to make, and the answer took a day, and then the three artifacts of the claim went on existing at three different rates.&lt;/p&gt;
&lt;h3 id=&quot;which-one-to-trust-if-you-can-find-just-one&quot;&gt;Which one to trust, if you can find just one&lt;/h3&gt;
&lt;p&gt;Here is the counter-intuitive result. The institution that got the announcement wrong left the clearest record of it. Bristol did not delete the URL. It replaced the story with a dated statement at the same address, so anyone who saved the link in 2019 and follows it in 2026 arrives at the correction. The URL that failed tells you that it failed, when, and why.&lt;/p&gt;
&lt;p&gt;The paper's record is honest and says less. It tells you the paper was reviewed and published and where. It does not tell you what happened next, and the record that would carry that information is empty.&lt;/p&gt;
&lt;p&gt;The coverage says whatever it said on the day, unless a particular editor came back, and you cannot tell from the page whether one did.&lt;/p&gt;
&lt;p&gt;If you can find just one of the three, find the press release. Not because it was right. Because its owner fixed it in place, and the others were never under any obligation to. And if you find the paper first, run the Crossref query before you cite it, because the absence of a notice is a fact about a registry, not about a manuscript.&lt;/p&gt;
&lt;p&gt;There are things this piece did not check. It did not evaluate the paper's linguistic argument; that is the specialists' work and they did it in 2019. It did not open two documents that would have added weight, Lisa Fagin Davis's own written response and a separately circulated critique of the journal's review, because both sit behind a login the tools used here cannot pass, so neither is quoted. It did not locate EurekAlert's copy of the release, which may carry a marker of its own. And it cannot know what was said between the university and the journal in the &quot;further discussions&quot; the statement promised. The record it can see is the one described above, and that record is the point.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Sourcing notes: every quotation carries a locator. Quotations from the paper are from the accepted author manuscript hosted by the University of Bristol's research portal, cited by PDF page, and the sentences were pulled from that PDF by&lt;/em&gt; &lt;code&gt;extract_manuscript_quotes.py&lt;/code&gt;&lt;em&gt;, shipped beside this file with the PDF; the journal's own pagination (30 to 67) was not used because the publisher's page could not be opened. The statement that the paper carries no retraction, correction or expression of concern is a reading of the Crossref record for its DOI on 3 September 2026, produced by&lt;/em&gt; &lt;code&gt;check_crossref_status.py&lt;/code&gt;&lt;em&gt;, also shipped beside this file. The press release's wording is quoted from surviving reproductions (Rare Book Hub, Language Log, Popular Archaeology), not from Bristol's site, which no longer carries it. Coverage pages were opened on 3 September 2026 and their update status is reported as found that day. No number in this piece was computed except those the two scripts print.&lt;/em&gt;*&lt;/p&gt;
&lt;h3 id=&quot;sources&quot;&gt;Sources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;University of Bristol, &quot;Statement re: Voynich research paper,&quot; 16 May 2019 (the whole page; the statement replaced the release at the same address). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.bristol.ac.uk/news/2019/may/voynich-manuscript.html&quot;&gt;https://www.bristol.ac.uk/news/2019/may/voynich-manuscript.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gerard Cheshire, &quot;The Language and Writing System of MS408 (Voynich) Explained,&quot; &lt;em&gt;Romance Studies&lt;/em&gt; 37:1 (2019), 30–67, published online 29 April 2019, DOI 10.1080/02639904.2019.1599566 (&lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.1080/02639904.2019.1599566&quot;&gt;https://doi.org/10.1080/02639904.2019.1599566&lt;/a&gt;); accepted author manuscript, University of Bristol Research Portal (abstract p. 2; the nun p. 3; Castello Aragonese and proto-Romance p. 4; the signed symbol key p. 48). &lt;a rel=&quot;nofollow&quot; href=&quot;https://research-information.bris.ac.uk/en/publications/the-language-and-writing-system-of-ms408-voynich-explained/&quot;&gt;https://research-information.bris.ac.uk/en/publications/the-language-and-writing-system-of-ms408-voynich-explained/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Crossref REST API record for the same DOI (fields &quot;updated-by&quot; and &quot;relation&quot;; read 3 September 2026): &lt;a rel=&quot;nofollow&quot; href=&quot;https://api.crossref.org/works/10.1080/02639904.2019.1599566&quot;&gt;https://api.crossref.org/works/10.1080/02639904.2019.1599566&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ryan F. Mandelbaum, &quot;Sorry, It Looks Like a Researcher Didn't Just Crack the Voynich Manuscript After All,&quot; Gizmodo, 17 May 2019 (the Davis and Kondrak quotations; the Bristol statement quoted). &lt;a rel=&quot;nofollow&quot; href=&quot;https://gizmodo.com/sorry-it-looks-like-a-researcher-didn-t-just-crack-the-1834841706&quot;&gt;https://gizmodo.com/sorry-it-looks-like-a-researcher-didn-t-just-crack-the-1834841706&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Michael Stillman, &quot;The Voynich Manuscript Code Has Finally Been Cracked...Or Not,&quot; Rare Book Hub, July 2019 (the release's first sentence; &quot;The following day…&quot;). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rarebookhub.com/articles/2636&quot;&gt;https://www.rarebookhub.com/articles/2636&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&quot;Bristol academic cracks Voynich code, solving century-old mystery of medieval text,&quot; University of Bristol news release as reproduced by Popular Archaeology, May 2019 (the two-weeks sentence; no correction present). &lt;a rel=&quot;nofollow&quot; href=&quot;https://popular-archaeology.com/article/bristol-academic-cracks-voynich-code-solving-century-old-mystery-of-medieval-text/&quot;&gt;https://popular-archaeology.com/article/bristol-academic-cracks-voynich-code-solving-century-old-mystery-of-medieval-text/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Victor Mair, &quot;Voynich code cracked?,&quot; Language Log, 16 May 2019 (paragraph 2, the release's opening sentence; paragraph 5, the journal and online date; the appended Guardian headline; comment 6 on proto-languages). &lt;a rel=&quot;nofollow&quot; href=&quot;https://languagelog.ldc.upenn.edu/nll/?p=42749&quot;&gt;https://languagelog.ldc.upenn.edu/nll/?p=42749&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;James Holloway, &quot;Has the medieval 'Voynich code' finally been cracked by a Bristol academic?,&quot; New Atlas, 16 May 2019, now hosted at Refractor (the editor's note quoting the Bristol statement). &lt;a rel=&quot;nofollow&quot; href=&quot;https://refractor.io/science/has-the-medieval-voynich-code-finally-been-cracked-by-a-bristol-academic/&quot;&gt;https://refractor.io/science/has-the-medieval-voynich-code-finally-been-cracked-by-a-bristol-academic/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;News Staff, &quot;Has Voynich Manuscript Been Decoded? Mysterious Book May Be Written in Proto-Romance Language,&quot; Sci-News, 15 May 2019 (opening sentences; no update present as of 3 September 2026). &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.sci.news/othersciences/linguistics/voynich-manuscript-07190.html&quot;&gt;https://www.sci.news/othersciences/linguistics/voynich-manuscript-07190.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jillian Foley, &quot;The Strange Quest to Crack the Voynich Code,&quot; Undark, 12 February 2020 (Newbold, 1921; &quot;every claim of a Voynich solution…&quot;). &lt;a rel=&quot;nofollow&quot; href=&quot;https://undark.org/2020/02/12/decoding-bizarre-voynich-manuscript/&quot;&gt;https://undark.org/2020/02/12/decoding-bizarre-voynich-manuscript/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wikipedia, &quot;Voynich manuscript,&quot; section &quot;Gerard Cheshire&quot; (used only to locate the contemporaneous coverage listed above; not quoted). &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Voynich_manuscript&quot;&gt;https://en.wikipedia.org/wiki/Voynich_manuscript&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26180/bristol-replaced-its-voynich-press-release-only-the-withdrawn-page-tells-you-what-happened</guid>
<pubDate>Fri, 04 Sep 2026 00:24:35 +0000</pubDate>
</item>
<item>
<title>Why I'm Building an Engineering Publishing Platform</title>
<link>https://coderlegion.com/25780/why-im-building-an-engineering-publishing-platform</link>
<description>&lt;p&gt;Over the past several months, I've made a commitment to write more.&lt;/p&gt;
&lt;p&gt;Not because I want to become a content creator, but because I've come to appreciate how much engineering knowledge quietly disappears every day. Architecture discussions end when the meeting is over. Production incidents become stories that fade with time. Lessons learned on one project rarely make it to the next. As engineers, we spend years building experience, but very little of it ever becomes knowledge that others can benefit from.&lt;/p&gt;
&lt;p&gt;Writing has become my way of changing that.&lt;/p&gt;
&lt;p&gt;Like many engineers, I started publishing wherever I could. I have a blog on my own website, along with accounts on Medium, Hashnode, DEV.to, and Coder Legion. Each platform reaches a different audience, and I genuinely like the idea of meeting engineers where they already are. If someone prefers reading on Hashnode instead of my website, that's perfectly fine with me. My goal has never been to drive traffic to a particular platform. It's to share ideas.&lt;/p&gt;
&lt;p&gt;Unfortunately, that turned out to be much harder than I expected.&lt;/p&gt;
&lt;p&gt;Every article followed the same routine. Once the writing was finished, I still had to copy Markdown between editors, upload images multiple times, recreate tags, tweak formatting, update SEO metadata, generate excerpts, and create social posts. By the time I finished publishing everywhere, I had spent almost as much time managing content as I had writing it.&lt;/p&gt;
&lt;p&gt;As an engineer, my first instinct was simple.&lt;/p&gt;
&lt;p&gt;Automate it.&lt;/p&gt;
&lt;p&gt;I envisioned a small service that would let me publish an article once and have it automatically appear everywhere else. My website would remain the source of truth while Medium, Hashnode, DEV.to, and every future platform would simply become destinations. It felt like a straightforward integration problem—build a few connectors, push the content out, and move on to the next article.&lt;/p&gt;
&lt;p&gt;Then I started designing it.&lt;/p&gt;
&lt;p&gt;Like many software projects, it didn't take long before I realized I was solving the wrong problem.&lt;/p&gt;
&lt;p&gt;The more I thought about the architecture, the less it resembled a collection of publishing scripts. If every platform was going to behave differently, I needed a common deployment model instead of a collection of one-off integrations. Articles would become immutable releases. Releases could be deployed to one or more publishing targets. Each destination would have its own connector responsible for understanding that platform, while the orchestration layer would manage deployments, retries, status, and lifecycle. The result was an architecture that felt much more like a software delivery pipeline than a blogging tool. &lt;/p&gt;
&lt;p&gt;That solved the technical problem.&lt;/p&gt;
&lt;p&gt;It didn't solve the real one.&lt;/p&gt;
&lt;p&gt;Somewhere in the middle of working through the design, I realized that publishing was never the difficult part.&lt;/p&gt;
&lt;p&gt;Thinking is.&lt;/p&gt;
&lt;p&gt;Every article I've written over the past few months has started long before a Markdown file ever existed. Sometimes it begins with a conversation. Sometimes it's a lesson I didn't fully appreciate until years later. Other times it's an architectural decision that seemed obvious in hindsight but required hours of discussion to reach. The words themselves are usually the final step in a much longer process of reflection.&lt;/p&gt;
&lt;p&gt;That's the process I don't want to lose.&lt;/p&gt;
&lt;p&gt;Most writing tools begin with a blank editor. They assume you already know exactly what you want to say. My experience has been almost the opposite. The most valuable part of writing is discovering the idea. It's connecting experiences that didn't seem related, questioning assumptions, and slowly uncovering the principle hidden underneath the story. By the time I begin writing, the real work has already happened.&lt;/p&gt;
&lt;p&gt;That realization changed the project entirely.&lt;/p&gt;
&lt;p&gt;I'm still building the cross-posting platform because I genuinely want to publish once and distribute everywhere with as little manual effort as possible. But that's no longer the vision.&lt;/p&gt;
&lt;p&gt;The vision is to build a platform that supports the entire journey from idea to publication. Conversations become drafts. Drafts become articles. Articles become releases that can be published anywhere. The platform should help organize ideas, generate summaries and metadata, manage revisions, and eventually understand the relationships between everything I've written. I don't want another tool that stores Markdown files. I want a system that helps preserve engineering judgment.&lt;/p&gt;
&lt;p&gt;Over the coming weeks, I'll be documenting the architecture, implementation, tradeoffs, and inevitable mistakes as I build this into the M²S² platform. Some articles will be technical deep dives into the connector architecture or deployment pipeline. Others will focus on the decisions that shaped the system and the lessons learned along the way. My hope is that readers don't just see the finished product—they understand the thinking behind it.&lt;/p&gt;
&lt;p&gt;If there's one thing I've learned throughout my career, it's that good engineering rarely starts with the perfect solution. More often, it starts with solving one problem, discovering a better question, and allowing the architecture to evolve alongside your understanding.&lt;/p&gt;
&lt;p&gt;That's exactly what happened here.&lt;/p&gt;
&lt;p&gt;I thought I was building a way to publish blog posts.&lt;/p&gt;
&lt;p&gt;Instead, I found myself designing a platform for capturing and sharing engineering experience.&lt;/p&gt;
&lt;p&gt;And I think that's a much more interesting problem to solve.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25780/why-im-building-an-engineering-publishing-platform</guid>
<pubDate>Sun, 30 Aug 2026 02:46:00 +0000</pubDate>
</item>
</channel>
</rss>