<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://yusupov.cloud/index.php?action=history&amp;feed=atom&amp;title=Echoes_of_What_Wasn%27t</id>
	<title>Echoes of What Wasn&#039;t - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://yusupov.cloud/index.php?action=history&amp;feed=atom&amp;title=Echoes_of_What_Wasn%27t"/>
	<link rel="alternate" type="text/html" href="https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;action=history"/>
	<updated>2026-05-16T11:51:00Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.0</generator>
	<entry>
		<id>https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=382&amp;oldid=prev</id>
		<title>Mvuijlst at 11:03, 12 April 2026</title>
		<link rel="alternate" type="text/html" href="https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=382&amp;oldid=prev"/>
		<updated>2026-04-12T11:03:05Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en-GB&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 11:03, 12 April 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Infobox&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Infobox&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;name &lt;/del&gt;        = Echoes of What Wasn&#039;t&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;01_name &lt;/ins&gt;        = Echoes of What Wasn&#039;t&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;url &lt;/del&gt;         = &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;{{URL|&lt;/del&gt;https://echoes.yusupov.cloud&lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;}}&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;02_url &lt;/ins&gt;         = https://echoes.yusupov.cloud&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;developer &lt;/del&gt;   = &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Operator&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;03_developer &lt;/ins&gt;   = &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;Michel Vuijlsteke&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;released &lt;/del&gt;    = 2025&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;04_released &lt;/ins&gt;    = 2025&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;genre &lt;/del&gt;       = AI-generated alternate-history newspaper&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;05_genre &lt;/ins&gt;       = AI-generated alternate-history newspaper&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;language &lt;/del&gt;    = Python&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;06_language &lt;/ins&gt;    = Python&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;framework &lt;/del&gt;   = [[Django]] 5.2 / [[Wagtail]] 7.3&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;07_framework &lt;/ins&gt;   = [[Django]] 5.2 / [[Wagtail]] 7.3&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;license &lt;/del&gt;     = Proprietary&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| &lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;08_license &lt;/ins&gt;     = Proprietary&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;}}&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;}}&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;br&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key yusupov:diff:1.41:old-381:rev-382:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>Mvuijlst</name></author>
	</entry>
	<entry>
		<id>https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=381&amp;oldid=prev</id>
		<title>Mvuijlst at 11:02, 12 April 2026</title>
		<link rel="alternate" type="text/html" href="https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=381&amp;oldid=prev"/>
		<updated>2026-04-12T11:02:03Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en-GB&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 11:02, 12 April 2026&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l1&quot;&gt;Line 1:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 1:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;−&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #ffe49c; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Infobox &lt;del style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;software&lt;/del&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;{{Infobox&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| name         = Echoes of What Wasn&amp;#039;t&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| name         = Echoes of What Wasn&amp;#039;t&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| url          = {{URL|https://echoes.yusupov.cloud}}&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;| url          = {{URL|https://echoes.yusupov.cloud}}&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;

&lt;!-- diff cache key yusupov:diff:1.41:old-380:rev-381:php=table --&gt;
&lt;/table&gt;</summary>
		<author><name>Mvuijlst</name></author>
	</entry>
	<entry>
		<id>https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=380&amp;oldid=prev</id>
		<title>Mvuijlst: Created page with &quot;{{Infobox software | name         = Echoes of What Wasn&#039;t | url          = {{URL|https://echoes.yusupov.cloud}} | developer    = Operator | released     = 2025 | genre        = AI-generated alternate-history newspaper | language     = Python | framework    = Django 5.2 / Wagtail 7.3 | license      = Proprietary }}  &#039;&#039;&#039;Echoes of What Wasn&#039;t&#039;&#039;&#039; (also known as &#039;&#039;&#039;Echoes&#039;&#039;&#039;) is a web application hosted at &lt;code&gt;echoes.yusupov.cloud&lt;/code&gt; that publishes AI-generated...&quot;</title>
		<link rel="alternate" type="text/html" href="https://yusupov.cloud/index.php?title=Echoes_of_What_Wasn%27t&amp;diff=380&amp;oldid=prev"/>
		<updated>2026-04-12T11:00:37Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;{{Infobox software | name         = Echoes of What Wasn&amp;#039;t | url          = {{URL|https://echoes.yusupov.cloud}} | developer    = Operator | released     = 2025 | genre        = AI-generated alternate-history newspaper | language     = Python | framework    = &lt;a href=&quot;/index.php?title=Django&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Django (page does not exist)&quot;&gt;Django&lt;/a&gt; 5.2 / &lt;a href=&quot;/index.php?title=Wagtail&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Wagtail (page does not exist)&quot;&gt;Wagtail&lt;/a&gt; 7.3 | license      = Proprietary }}  &amp;#039;&amp;#039;&amp;#039;Echoes of What Wasn&amp;#039;t&amp;#039;&amp;#039;&amp;#039; (also known as &amp;#039;&amp;#039;&amp;#039;Echoes&amp;#039;&amp;#039;&amp;#039;) is a web application hosted at &amp;lt;code&amp;gt;echoes.yusupov.cloud&amp;lt;/code&amp;gt; that publishes AI-generated...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{Infobox software&lt;br /&gt;
| name         = Echoes of What Wasn&amp;#039;t&lt;br /&gt;
| url          = {{URL|https://echoes.yusupov.cloud}}&lt;br /&gt;
| developer    = Operator&lt;br /&gt;
| released     = 2025&lt;br /&gt;
| genre        = AI-generated alternate-history newspaper&lt;br /&gt;
| language     = Python&lt;br /&gt;
| framework    = [[Django]] 5.2 / [[Wagtail]] 7.3&lt;br /&gt;
| license      = Proprietary&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Echoes of What Wasn&amp;#039;t&amp;#039;&amp;#039;&amp;#039; (also known as &amp;#039;&amp;#039;&amp;#039;Echoes&amp;#039;&amp;#039;&amp;#039;) is a web application hosted at &amp;lt;code&amp;gt;echoes.yusupov.cloud&amp;lt;/code&amp;gt; that publishes AI-generated alternate-history newspaper and magazine articles. Each article takes a real historical event, invents a plausible point of divergence, and presents the consequences as an in-world retrospective written decades later by a fictitious journalist for a fictitious period-appropriate publication. The site&amp;#039;s tagline is &amp;quot;Dispatches from Histories That Never Were.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Technology stack ==&lt;br /&gt;
&lt;br /&gt;
The application is built on Django 5.2 with Wagtail 7.3 as its content management system.&amp;lt;ref name=&amp;quot;requirements&amp;quot;&amp;gt;requirements.txt in the project repository lists Django 5.2.12 and Wagtail 7.3.1.&amp;lt;/ref&amp;gt; It uses [[Django REST framework]] for a JSON API, [[SQLite]] as its database, and is deployed behind [[Nginx]] with [[Gunicorn]] on a Linux server. Additional dependencies include [[Pillow (imaging library)|Pillow]] for image processing, [[nh3 (library)|nh3]] for HTML sanitisation, [[Beautiful Soup (HTML parser)|Beautiful Soup]] and [[lxml]] for scraping, and the [[OpenAI]] Python client for language-model and image-generation calls. Geographic features use &amp;lt;code&amp;gt;wagtailgeowidget&amp;lt;/code&amp;gt; with Google Maps integration.&lt;br /&gt;
&lt;br /&gt;
== Data model ==&lt;br /&gt;
&lt;br /&gt;
=== Article pages ===&lt;br /&gt;
&lt;br /&gt;
Each article is a Wagtail &amp;lt;code&amp;gt;ArticlePage&amp;lt;/code&amp;gt;, a child of a single &amp;lt;code&amp;gt;ArticleIndexPage&amp;lt;/code&amp;gt; in the page tree. An article carries:&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;title&amp;#039;&amp;#039;, &amp;#039;&amp;#039;subtitle&amp;#039;&amp;#039;, &amp;#039;&amp;#039;publication&amp;#039;&amp;#039; (the fictitious newspaper/magazine name), &amp;#039;&amp;#039;date&amp;#039;&amp;#039; (in-universe publication date), &amp;#039;&amp;#039;event_date&amp;#039;&amp;#039; (the historical event&amp;#039;s date), &amp;#039;&amp;#039;location&amp;#039;&amp;#039;, and a foreign key to an &amp;lt;code&amp;gt;Author&amp;lt;/code&amp;gt; snippet (exposed via the API as &amp;#039;&amp;#039;byline&amp;#039;&amp;#039;).&lt;br /&gt;
* A &amp;#039;&amp;#039;body_richtext&amp;#039;&amp;#039; field (Wagtail &amp;lt;code&amp;gt;RichTextField&amp;lt;/code&amp;gt;) containing the article&amp;#039;s HTML body, sanitised on ingest.&lt;br /&gt;
* A &amp;#039;&amp;#039;body&amp;#039;&amp;#039; [[Wagtail StreamField|StreamField]] supporting paragraph, heading, callout, quote, pull-quote, bulleted list, numbered list, image, aside (with its own nested stream), and horizontal-rule block types.&lt;br /&gt;
* Separate &amp;#039;&amp;#039;callouts&amp;#039;&amp;#039; and &amp;#039;&amp;#039;quotes&amp;#039;&amp;#039; StreamFields for editorial pull-outs and attributed quotations.&lt;br /&gt;
* A JSON &amp;#039;&amp;#039;assets&amp;#039;&amp;#039; field listing image metadata (src, alt, caption, credit, prompt).&lt;br /&gt;
* A &amp;#039;&amp;#039;featured_image&amp;#039;&amp;#039; foreign key to a custom image model (&amp;lt;code&amp;gt;CustomImage&amp;lt;/code&amp;gt;, extending Wagtail&amp;#039;s &amp;lt;code&amp;gt;AbstractImage&amp;lt;/code&amp;gt; with a description field).&lt;br /&gt;
* Internal editorial fields: &amp;#039;&amp;#039;original_event&amp;#039;&amp;#039;, &amp;#039;&amp;#039;departure_point&amp;#039;&amp;#039;, and &amp;#039;&amp;#039;ai_context&amp;#039;&amp;#039;, which are visible in the Wagtail admin but not rendered on the public site.&lt;br /&gt;
* Geographic fields: &amp;#039;&amp;#039;event_location&amp;#039;&amp;#039; (address string), &amp;#039;&amp;#039;geo_location&amp;#039;&amp;#039; (WKT point), &amp;#039;&amp;#039;latitude&amp;#039;&amp;#039;, and &amp;#039;&amp;#039;longitude&amp;#039;&amp;#039;. The model&amp;#039;s &amp;lt;code&amp;gt;save()&amp;lt;/code&amp;gt; method synchronises the WKT point and decimal-degree fields bidirectionally.&lt;br /&gt;
&lt;br /&gt;
=== Historical events ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;HistoricalEvent&amp;lt;/code&amp;gt; model stores real events scraped from Wikipedia, keyed by ISO date, astronomical year, event text, language code, and a boolean &amp;#039;&amp;#039;used&amp;#039;&amp;#039; flag.&lt;br /&gt;
&lt;br /&gt;
=== Custom images ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;CustomImage&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;CustomRendition&amp;lt;/code&amp;gt; extend Wagtail&amp;#039;s abstract image models to add a &amp;#039;&amp;#039;description&amp;#039;&amp;#039; text field, used to store the original DALL·E prompt.&lt;br /&gt;
&lt;br /&gt;
== Article generation pipeline ==&lt;br /&gt;
&lt;br /&gt;
Article generation is performed by the standalone script &amp;lt;code&amp;gt;_generate_article.py&amp;lt;/code&amp;gt;, which orchestrates a six-step pipeline of OpenAI API calls, checkpoint-resumed so that a failure at any step does not waste prior work. The default text model is GPT-5 (with GPT-4o as fallback); the image model is &amp;lt;code&amp;gt;gpt-image-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Step 0: Event sourcing ===&lt;br /&gt;
&lt;br /&gt;
A separate scraper (&amp;lt;code&amp;gt;_scrape_wikipedia_events.py&amp;lt;/code&amp;gt;) fetches every day-of-year page from Wikipedia (e.g. &amp;quot;January 1&amp;quot;, &amp;quot;February 14&amp;quot;) for English, French, and Dutch editions. It parses the &amp;quot;Events&amp;quot; sections, extracts each entry&amp;#039;s year and text, and stores them as &amp;lt;code&amp;gt;HistoricalEvent&amp;lt;/code&amp;gt; rows. The scraper respects rate limits and identifies itself via a custom User-Agent string.&lt;br /&gt;
&lt;br /&gt;
=== Step 1: Event selection ===&lt;br /&gt;
&lt;br /&gt;
The pipeline queries the database for unused Common Era events whose month and day match the current date and whose year is at least 50 years in the past. Five candidates are sampled at random and presented in a structured prompt to the text model, which acts as an &amp;quot;editorial planner.&amp;quot; The model returns JSON selecting the single most promising event for an alternate-history feature, together with a proposed divergence date, publication date, publication context (name, type, city, editorial stance, intended readership), article angle, and a list of visual possibilities. The pipeline validates that the chosen event ID was in the candidate set.&lt;br /&gt;
&lt;br /&gt;
=== Step 2: Timeline brief ===&lt;br /&gt;
&lt;br /&gt;
The selection JSON is fed into a second prompt that asks the model to build an &amp;quot;internal dossier&amp;quot; — a compact alternate-history world brief. This includes:&lt;br /&gt;
&lt;br /&gt;
* A core timeline premise.&lt;br /&gt;
* A chronological &amp;#039;&amp;#039;historical_path&amp;#039;&amp;#039; of at least five entries spanning from divergence to publication date.&lt;br /&gt;
* Canonical facts to preserve (real-world events outside the divergence&amp;#039;s causal chain).&lt;br /&gt;
* &amp;quot;Real-history traps to avoid&amp;quot; — events whose preconditions are disrupted by the divergence.&lt;br /&gt;
* In-world assumptions (what ordinary readers take for granted).&lt;br /&gt;
* Named entities (people, institutions, treaties, technologies) specific to this timeline.&lt;br /&gt;
* An article brief (genre, tone, writer persona, central question, thesis, section ideas).&lt;br /&gt;
* Style constraints (bans on meta-framing, &amp;quot;not X but Y&amp;quot; constructions, poetic codas, excessive em-dashes and exclamation marks).&lt;br /&gt;
* A visual brief for the photo editor.&lt;br /&gt;
&lt;br /&gt;
This step enforces a &amp;quot;proportional divergence rule&amp;quot;: the timeline should change only what the divergence actually changes, and leave causally unrelated real-world events intact.&lt;br /&gt;
&lt;br /&gt;
=== Step 3: Article generation ===&lt;br /&gt;
&lt;br /&gt;
The selection and timeline-brief JSONs are combined in a third prompt which instructs the model to write the full article as a single JSON object. The article must:&lt;br /&gt;
&lt;br /&gt;
* Be written entirely from inside the alternate timeline, with no meta-framing or comparison to real history.&lt;br /&gt;
* Be at least 1,500 words across its &amp;lt;code&amp;gt;body_blocks&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Include at least two callout blocks and one attributed quote block, interleaved naturally.&lt;br /&gt;
* Use period-appropriate prose matching the in-world date, place, and publication culture.&lt;br /&gt;
* Be entirely in the language of the source event (English, French, or Dutch).&lt;br /&gt;
* Include a hero-image prompt (in English) and geographic coordinates.&lt;br /&gt;
&lt;br /&gt;
The model is asked to self-check for in-world consistency, language correctness, and structural validity before outputting. If the returned JSON is malformed, a repair prompt asks the model to fix it.&lt;br /&gt;
&lt;br /&gt;
=== Step 4: In-world edit ===&lt;br /&gt;
&lt;br /&gt;
The draft JSON is passed through a fourth &amp;quot;line editor&amp;quot; prompt. This step:&lt;br /&gt;
&lt;br /&gt;
* Preserves meaning, canon, and timeline integrity.&lt;br /&gt;
* Removes AI-flavored rhetoric (&amp;quot;not X, but Y&amp;quot; constructions, pseudo-poetic endings, inflated abstraction, repetitive thesis phrasing).&lt;br /&gt;
* Replaces any accidental references to real-world events that should not exist in the diverged timeline.&lt;br /&gt;
* Forces the canonical event and publication dates from the selection step.&lt;br /&gt;
* Populates the internal &amp;#039;&amp;#039;original_event&amp;#039;&amp;#039;, &amp;#039;&amp;#039;departure_point&amp;#039;&amp;#039;, and &amp;#039;&amp;#039;ai_context&amp;#039;&amp;#039; fields.&lt;br /&gt;
&lt;br /&gt;
The pipeline then validates the edited payload: checking required fields, enforcing the ban on meta-themed publication names, verifying date consistency with the selection, confirming minimum callout and quote counts, and ensuring no quote is attributed to the article&amp;#039;s own author.&lt;br /&gt;
&lt;br /&gt;
=== Step 5: Image prompts ===&lt;br /&gt;
&lt;br /&gt;
A fifth prompt, addressed to a &amp;quot;photo editor and visual archivist,&amp;quot; generates one to five image concepts. Each concept specifies:&lt;br /&gt;
&lt;br /&gt;
* A role (hero or supporting).&lt;br /&gt;
* An aspect ratio (landscape, portrait, or square).&lt;br /&gt;
* A detailed English-language image prompt.&lt;br /&gt;
* Alt text, caption, and credit in the article language.&lt;br /&gt;
&lt;br /&gt;
The prompt includes extensive rules for period-appropriate visual media (daguerreotype for 1840–1880, silver gelatin for 1880–1930, Kodachrome for 1970–2000, and so on), and bans on text in images, symmetrical compositions, &amp;quot;dramatic lighting,&amp;quot; and modern digital-art aesthetics. The returned concepts are normalised: roles are deduplicated, aspect ratios are validated, and the set is capped at five images.&lt;br /&gt;
&lt;br /&gt;
=== Step 6: Image generation and upload ===&lt;br /&gt;
&lt;br /&gt;
Each image prompt is wrapped in additional instructions enforcing candid documentary realism (or authentic non-photographic media for paintings, engravings, and archival objects) before being sent to OpenAI&amp;#039;s image-generation endpoint (&amp;lt;code&amp;gt;gpt-image-1&amp;lt;/code&amp;gt;). Generated images are decoded from base64, converted to optimised progressive JPEG via Pillow, and uploaded through the application&amp;#039;s &amp;lt;code&amp;gt;/api/media&amp;lt;/code&amp;gt; endpoint. The hero image is set as the article&amp;#039;s &amp;#039;&amp;#039;featured_image&amp;#039;&amp;#039;. Non-hero images are inserted into the &amp;lt;code&amp;gt;body_blocks&amp;lt;/code&amp;gt; array at evenly spaced positions.&lt;br /&gt;
&lt;br /&gt;
=== Step 7: Publication ===&lt;br /&gt;
&lt;br /&gt;
The assembled JSON payload — with body blocks, assets, featured image, byline, callouts, quotes, and editorial metadata — is POSTed to the &amp;lt;code&amp;gt;/api/articles&amp;lt;/code&amp;gt; endpoint. The API serializer sanitises body HTML via nh3, resolves image file paths to Wagtail embed tags, creates or looks up the &amp;lt;code&amp;gt;Author&amp;lt;/code&amp;gt; snippet, attaches the article as a child of the &amp;lt;code&amp;gt;ArticleIndexPage&amp;lt;/code&amp;gt;, and publishes it. The source &amp;lt;code&amp;gt;HistoricalEvent&amp;lt;/code&amp;gt; is marked as used. The checkpoint file is cleared.&lt;br /&gt;
&lt;br /&gt;
== REST API ==&lt;br /&gt;
&lt;br /&gt;
The application exposes a JSON API under &amp;lt;code&amp;gt;/api/&amp;lt;/code&amp;gt;, protected by bearer-token authentication for write operations and publicly readable:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;GET/POST /api/articles&amp;lt;/code&amp;gt; — list (paginated) or create articles.&lt;br /&gt;
* &amp;lt;code&amp;gt;GET/PUT /api/articles/&amp;lt;id&amp;gt;&amp;lt;/code&amp;gt; — retrieve or update a single article.&lt;br /&gt;
* &amp;lt;code&amp;gt;POST /api/media&amp;lt;/code&amp;gt; — upload a base64-encoded image, which is stored as a &amp;lt;code&amp;gt;CustomImage&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;GET /api/markers&amp;lt;/code&amp;gt; — return geographic markers for all geolocated articles.&lt;br /&gt;
* &amp;lt;code&amp;gt;GET /api/schema&amp;lt;/code&amp;gt; — serve the OpenAPI specification.&lt;br /&gt;
&lt;br /&gt;
Public documentation is available at &amp;lt;code&amp;gt;/docs&amp;lt;/code&amp;gt; (ReDoc) and &amp;lt;code&amp;gt;/swagger&amp;lt;/code&amp;gt; (Swagger UI).&lt;br /&gt;
&lt;br /&gt;
== Public interface ==&lt;br /&gt;
&lt;br /&gt;
=== Home page ===&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;ArticleIndexPage&amp;lt;/code&amp;gt; serves as the site&amp;#039;s home page, displaying up to seven recent articles, a &amp;quot;picture desk&amp;quot; mosaic of up to fifteen images (sampled hourly for variety), a map teaser with up to four geolocated articles, and navigation links for monthly archives.&lt;br /&gt;
&lt;br /&gt;
=== Article pages ===&lt;br /&gt;
&lt;br /&gt;
Each article page features a full-bleed hero image (when available), a masthead with the fictitious publication name, headline, subtitle, byline, dateline, and the event date. The body renders the StreamField blocks: paragraphs, headings, images with captions and credits, callouts, attributed quotes, pull quotes, bulleted and numbered lists, asides with nested content, and horizontal rules.&lt;br /&gt;
&lt;br /&gt;
=== Where/When ===&lt;br /&gt;
&lt;br /&gt;
An interactive &amp;quot;Where &amp;amp; When&amp;quot; page presents all geolocated articles on a [[Leaflet (software)|Leaflet]] map (using CartoDB tiles with light/dark themes) alongside a zoomable timeline. Articles are plotted by their event location and date. The map and article data are loaded via the &amp;lt;code&amp;gt;/api/markers&amp;lt;/code&amp;gt; endpoint.&lt;br /&gt;
&lt;br /&gt;
=== Monthly archives ===&lt;br /&gt;
&lt;br /&gt;
Articles can be browsed by the month of their in-universe event date. The index page dynamically builds archive links with article counts per month.&lt;br /&gt;
&lt;br /&gt;
=== Theme ===&lt;br /&gt;
&lt;br /&gt;
The site supports light and dark colour themes, toggled via a button in the masthead and persisted in &amp;lt;code&amp;gt;localStorage&amp;lt;/code&amp;gt;. Typography uses Playfair Display for display headings, Source Serif 4 for body text, and Inter for UI elements.&lt;br /&gt;
&lt;br /&gt;
== Multilingual support ==&lt;br /&gt;
&lt;br /&gt;
The Wikipedia scraper and article-generation pipeline support English, French, and Dutch. The language of each historical event is stored in the database and carried through to the generation prompts, which instruct the model to write all visible article content — title, subtitle, body, captions, quotes, and credits — in the source event&amp;#039;s language. Internal editorial fields and image prompts remain in English.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Alternate history]]&lt;br /&gt;
* [[Wagtail (CMS)]]&lt;br /&gt;
* [[OpenAI]]&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
{{reflist}}&lt;/div&gt;</summary>
		<author><name>Mvuijlst</name></author>
	</entry>
</feed>