diff --git a/README.md b/README.md
index 527075d..71043de 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ shared/
models/ # Canonical domain models
user.py # User
magic_link.py # MagicLink (auth tokens)
- domain_event.py # DomainEvent (legacy — being removed)
+ (domain_event.py removed — table dropped, see migration n4l2i8j0k1)
kv.py # KeyValue (key-value store)
menu_item.py # MenuItem (deprecated — use MenuNode)
menu_node.py # MenuNode (navigation tree)
diff --git a/browser/templates/_types/blog/_action_buttons.html b/browser/templates/_types/blog/_action_buttons.html
deleted file mode 100644
index 0ea7fa2..0000000
--- a/browser/templates/_types/blog/_action_buttons.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{# New Post + Drafts toggle — shown in aside (desktop + mobile) #}
-
- {% if has_access('blog.new_post') %}
- {% set new_href = url_for('blog.new_post')|host %}
-
- New Post
-
- {% endif %}
- {% if g.user and (draft_count or drafts) %}
- {% if drafts %}
- {% set drafts_off_href = (current_local_href ~ {'drafts': None}|qs)|host %}
-
- Drafts
- {{ draft_count }}
-
- {% else %}
- {% set drafts_on_href = (current_local_href ~ {'drafts': '1'}|qs)|host %}
-
- Drafts
- {{ draft_count }}
-
- {% endif %}
- {% endif %}
-
diff --git a/browser/templates/_types/blog/_main_panel.html b/browser/templates/_types/blog/_main_panel.html
deleted file mode 100644
index 350999d..0000000
--- a/browser/templates/_types/blog/_main_panel.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
- {# View toggle bar - desktop only #}
-
-
-
- {# Calendar grid #}
-
- {# Weekday header: only show on sm+ (desktop/tablet) #}
-
- {% for wd in weekday_names %}
-
{{ wd }}
- {% endfor %}
-
-
- {# On mobile: 1 column; on sm+: 7 columns #}
-
- {% for week in weeks %}
- {% for day in week %}
-
-
-
-
- {{ day.date.strftime('%a') }}
-
-
- {# Clickable day number: goes to day detail view #}
-
- {{ day.date.day }}
-
-
-
- {# Entries for this day: merged, chronological #}
-
- {# Build a list of entries for this specific day.
- month_entries is already sorted by start_at in Python. #}
- {% for e in month_entries %}
- {% if e.start_at.date() == day.date %}
- {# Decide colour: highlight "mine" differently if you want #}
- {% set is_mine = (g.user and e.user_id == g.user.id)
- or (not g.user and e.session_id == qsession.get('calendar_sid')) %}
-
-
- {{ e.name }}
-
-
- {{ (e.state or 'pending')|replace('_', ' ') }}
-
-
- {% endif %}
- {% endfor %}
-
-
-
- {% endfor %}
- {% endfor %}
-
-
diff --git a/browser/templates/_types/calendar/admin/_description.html b/browser/templates/_types/calendar/admin/_description.html
deleted file mode 100644
index 7fc96f0..0000000
--- a/browser/templates/_types/calendar/admin/_description.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
- {% if calendar.description %}
-
- {{ calendar.description }}
-
- {% else %}
-
- No description yet.
-
- {% endif %}
-
-
-
-
-
-
-{% if oob %}
-
- {% from '_types/calendar/_description.html' import description %}
- {{description(calendar, oob=True)}}
-{% endif %}
-
-
diff --git a/browser/templates/_types/calendar/admin/_description_edit.html b/browser/templates/_types/calendar/admin/_description_edit.html
deleted file mode 100644
index 61c5ff0..0000000
--- a/browser/templates/_types/calendar/admin/_description_edit.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
diff --git a/browser/templates/_types/calendar/admin/_main_panel.html b/browser/templates/_types/calendar/admin/_main_panel.html
deleted file mode 100644
index 9696f47..0000000
--- a/browser/templates/_types/calendar/admin/_main_panel.html
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
Calendar configuration
-
-
-
- Description
-
- {% include '_types/calendar/admin/_description.html' %}
-
-
-
-
-
-
Description
-
{{calendar.description or ''}}
-
{{ (calendar.description or '') }}
-
-
-
- Save
-
-
-
-
-
-
-
diff --git a/browser/templates/_types/calendar/admin/header/_header.html b/browser/templates/_types/calendar/admin/header/_header.html
deleted file mode 100644
index d383373..0000000
--- a/browser/templates/_types/calendar/admin/header/_header.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='calendar-admin-row', oob=oob) %}
- {% call links.link(
- url_for('calendars.calendar.admin.admin', slug=post.slug, calendar_slug=calendar.slug),
- hx_select_search
- ) %}
- {{ links.admin() }}
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/calendar/admin/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
\ No newline at end of file
diff --git a/browser/templates/_types/calendar/header/_header.html b/browser/templates/_types/calendar/header/_header.html
deleted file mode 100644
index 73fe115..0000000
--- a/browser/templates/_types/calendar/header/_header.html
+++ /dev/null
@@ -1,23 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='calendar-row', oob=oob) %}
-
-
-
-
-
- {{ calendar.name }}
-
-
- {% from '_types/calendar/_description.html' import description %}
- {{description(calendar)}}
-
-
- {% call links.desktop_nav() %}
- {% include '_types/calendar/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
-
-
-
diff --git a/browser/templates/_types/calendar/index.html b/browser/templates/_types/calendar/index.html
deleted file mode 100644
index 802c45c..0000000
--- a/browser/templates/_types/calendar/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends '_types/post/index.html' %}
-
-{% block post_header_child %}
- {% from '_types/root/_n/macros.html' import index_row with context %}
- {% call index_row('calendar-header-child', '_types/calendar/header/_header.html') %}
- {% block calendar_header_child %}
- {% endblock %}
- {% endcall %}
-{% endblock %}
-
-
-{% block _main_mobile_menu %}
- {% include '_types/calendar/_nav.html' %}
-{% endblock %}
-
-
-
-{% block content %}
- {% include '_types/calendar/_main_panel.html' %}
-{% endblock %}
diff --git a/browser/templates/_types/day/_add.html b/browser/templates/_types/day/_add.html
deleted file mode 100644
index df02331..0000000
--- a/browser/templates/_types/day/_add.html
+++ /dev/null
@@ -1,301 +0,0 @@
-
-
-
-
-
- {# 1) Entry name #}
-
-
- {# 2) Slot picker for this weekday (required) #}
- {% if day_slots %}
-
- {% for slot in day_slots %}
-
- {{ slot.name }}
- ({{ slot.time_start.strftime('%H:%M') }}
- {% if slot.time_end %}–{{ slot.time_end.strftime('%H:%M') }}{% else %}–open-ended{% endif %})
- {% if slot | getattr('flexible', False) %}[flexible]{% endif %}
-
- {% endfor %}
-
- {% else %}
-
- No slots defined for this day.
-
- {% endif %}
-
- {# 3) Time entry + cost display #}
-
- {# Time inputs — hidden until a flexible slot is selected #}
-
-
- From
-
-
-
- To
-
-
-
-
-
- {# Cost display — shown when a slot is selected #}
-
- Estimated Cost: £0.00
-
-
- {# Summary of fixed times — shown for non-flexible slots #}
-
-
-
- {# Ticket Configuration #}
-
-
Ticket Configuration (Optional)
-
-
-
-
-
- Cancel
-
-
-
-
- Save entry
-
-
-
-
-{# --- Behaviour: lock / unlock times based on slot.flexible --- #}
-
\ No newline at end of file
diff --git a/browser/templates/_types/day/_add_button.html b/browser/templates/_types/day/_add_button.html
deleted file mode 100644
index 46726e5..0000000
--- a/browser/templates/_types/day/_add_button.html
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- + Add entry
-
diff --git a/browser/templates/_types/day/_nav.html b/browser/templates/_types/day/_nav.html
deleted file mode 100644
index cfb8aca..0000000
--- a/browser/templates/_types/day/_nav.html
+++ /dev/null
@@ -1,50 +0,0 @@
-{% import 'macros/links.html' as links %}
-
-{# Confirmed Entries - vertical on mobile, horizontal with arrows on desktop #}
-
-
-{# Container nav widgets (market links, etc.) #}
-{% if container_nav_widgets %}
- {% for wdata in container_nav_widgets %}
- {% with ctx=wdata.ctx %}
- {% include wdata.widget.template with context %}
- {% endwith %}
- {% endfor %}
-{% endif %}
-
-{# Admin link #}
-{% if g.rights.admin %}
- {% from 'macros/admin_nav.html' import admin_nav_item %}
- {{admin_nav_item(
- url_for(
- 'calendars.calendar.day.admin.admin',
- slug=post.slug,
- calendar_slug=calendar.slug,
- year=day_date.year,
- month=day_date.month,
- day=day_date.day
- )
- )}}
-{% endif %}
\ No newline at end of file
diff --git a/browser/templates/_types/day/_row.html b/browser/templates/_types/day/_row.html
deleted file mode 100644
index 87aead7..0000000
--- a/browser/templates/_types/day/_row.html
+++ /dev/null
@@ -1,76 +0,0 @@
-{% import 'macros/links.html' as links %}
-
-
-
- {% call links.link(
- url_for(
- 'calendars.calendar.day.calendar_entries.calendar_entry.get',
- slug=post.slug,
- calendar_slug=calendar.slug,
- day=day,
- month=month,
- year=year,
- entry_id=entry.id
- ),
- hx_select_search,
- aclass=styles.pill
- ) %}
- {{ entry.name }}
- {% endcall %}
-
-
-
- {% if entry.slot %}
-
- {% call links.link(
- url_for(
- 'calendars.calendar.slots.slot.get',
- slug=post.slug,
- calendar_slug=calendar.slug,
- slot_id=entry.slot.id
- ),
- hx_select_search,
- aclass=styles.pill
- ) %}
- {{ entry.slot.name }}
- {% endcall %}
-
- ({{ entry.slot.time_start.strftime('%H:%M') }}{% if entry.slot.time_end %} → {{ entry.slot.time_end.strftime('%H:%M') }}{% endif %})
-
-
- {% else %}
-
- {% include '_types/entry/_times.html' %}
-
- {% endif %}
-
-
-
- {% include '_types/entry/_state.html' %}
-
-
-
-
- £{{ ('%.2f'|format(entry.cost)) if entry.cost is not none else '0.00' }}
-
-
-
- {% if entry.ticket_price is not none %}
-
-
£{{ ('%.2f'|format(entry.ticket_price)) }}
-
- {% if entry.ticket_count is not none %}
- {{ entry.ticket_count }} tickets
- {% else %}
- Unlimited
- {% endif %}
-
-
- {% else %}
- No tickets
- {% endif %}
-
-
- {% include '_types/entry/_options.html' %}
-
-
\ No newline at end of file
diff --git a/browser/templates/_types/day/admin/_nav_entries_oob.html b/browser/templates/_types/day/admin/_nav_entries_oob.html
deleted file mode 100644
index d72fc90..0000000
--- a/browser/templates/_types/day/admin/_nav_entries_oob.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{# OOB swap for day confirmed entries nav when entries are edited #}
-{% import 'macros/links.html' as links %}
-
-{# Confirmed Entries - vertical on mobile, horizontal with arrows on desktop #}
-{% if confirmed_entries %}
-
-{% else %}
- {# Empty placeholder to remove nav entries when none are confirmed #}
-
-{% endif %}
diff --git a/browser/templates/_types/day/admin/header/_header.html b/browser/templates/_types/day/admin/header/_header.html
deleted file mode 100644
index b5f583c..0000000
--- a/browser/templates/_types/day/admin/header/_header.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='day-admin-row', oob=oob) %}
- {% call links.link(
- url_for(
- 'calendars.calendar.day.admin.admin',
- slug=post.slug,
- calendar_slug=calendar.slug,
- year=day_date.year,
- month=day_date.month,
- day=day_date.day
- ),
- hx_select_search
- ) %}
- {{ links.admin() }}
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/day/admin/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
\ No newline at end of file
diff --git a/browser/templates/_types/day/header/_header.html b/browser/templates/_types/day/header/_header.html
deleted file mode 100644
index e53a815..0000000
--- a/browser/templates/_types/day/header/_header.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='day-row', oob=oob) %}
- {% call links.link(
- url_for(
- 'calendars.calendar.day.show_day',
- slug=post.slug,
- calendar_slug=calendar.slug,
- year=day_date.year,
- month=day_date.month,
- day=day_date.day
- ),
- hx_select_search,
- ) %}
-
-
- {{ day_date.strftime('%A %d %B %Y') }}
-
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/day/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
-
-
-
diff --git a/browser/templates/_types/day/index.html b/browser/templates/_types/day/index.html
deleted file mode 100644
index 655ee55..0000000
--- a/browser/templates/_types/day/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends '_types/calendar/index.html' %}
-
-{% block calendar_header_child %}
- {% from '_types/root/_n/macros.html' import index_row with context %}
- {% call index_row('day-header-child', '_types/day/header/_header.html') %}
- {% block day_header_child %}
- {% endblock %}
- {% endcall %}
-{% endblock %}
-
-{% block _main_mobile_menu %}
- {% include '_types/day/_nav.html' %}
-{% endblock %}
-
-
-{% block content %}
- {% include '_types/day/_main_panel.html' %}
-{% endblock %}
diff --git a/browser/templates/_types/entry/_edit.html b/browser/templates/_types/entry/_edit.html
deleted file mode 100644
index 628f239..0000000
--- a/browser/templates/_types/entry/_edit.html
+++ /dev/null
@@ -1,334 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Name
-
-
-
-
-
-
-
- Slot
-
- {% if day_slots %}
-
- {% for slot in day_slots %}
-
- {{ slot.name }}
- ({{ slot.time_start.strftime('%H:%M') }}
- {% if slot.time_end %}–{{ slot.time_end.strftime('%H:%M') }}{% else %}–open-ended{% endif %})
- {% if slot.flexible %}[flexible]{% endif %}
-
- {% endfor %}
-
- {% else %}
-
- No slots defined for this day.
-
- {% endif %}
-
-
-
-
-
-
- From
-
-
-
-
-
-
- To
-
-
-
-
-
-
-
-
-
-
-
-
- Estimated Cost: £{{ ('%.2f'|format(entry.cost)) if entry.cost is not none else '0.00' }}
-
-
-
-
-
Ticket Configuration
-
-
-
-
- Ticket Price (£)
-
-
-
Leave empty if no tickets needed
-
-
-
-
- Total Tickets
-
-
-
Leave empty for unlimited
-
-
-
-
-
-
-
-
- Cancel
-
-
-
-
-
- Save entry
-
-
-
-
-
-
-
-{# --- Behaviour: lock / unlock times based on slot.flexible --- #}
-
\ No newline at end of file
diff --git a/browser/templates/_types/entry/_main_panel.html b/browser/templates/_types/entry/_main_panel.html
deleted file mode 100644
index 403605b..0000000
--- a/browser/templates/_types/entry/_main_panel.html
+++ /dev/null
@@ -1,126 +0,0 @@
-
-
-
-
-
- Name
-
-
- {{ entry.name }}
-
-
-
-
-
-
- Slot
-
-
- {% if entry.slot %}
-
- {{ entry.slot.name }}
-
- {% if entry.slot.flexible %}
- (flexible)
- {% else %}
- (fixed)
- {% endif %}
- {% else %}
- No slot assigned
- {% endif %}
-
-
-
-
-
-
- Time Period
-
-
- {{ entry.start_at.strftime('%H:%M') }}
- {% if entry.end_at %}
- – {{ entry.end_at.strftime('%H:%M') }}
- {% else %}
- – open-ended
- {% endif %}
-
-
-
-
-
-
- State
-
-
-
- {% include '_types/entry/_state.html' %}
-
-
-
-
-
-
-
- Cost
-
-
-
- £{{ ('%.2f'|format(entry.cost)) if entry.cost is not none else '0.00' }}
-
-
-
-
-
-
-
- Tickets
-
-
- {% include '_types/entry/_tickets.html' %}
-
-
-
-
-
-
- Date
-
-
- {{ entry.start_at.strftime('%A, %B %d, %Y') }}
-
-
-
-
-
-
- Associated Posts
-
-
- {% include '_types/entry/_posts.html' %}
-
-
-
-
-
- {% include '_types/entry/_options.html' %}
-
-
- Edit
-
-
-
-
\ No newline at end of file
diff --git a/browser/templates/_types/entry/_nav.html b/browser/templates/_types/entry/_nav.html
deleted file mode 100644
index 388fc2e..0000000
--- a/browser/templates/_types/entry/_nav.html
+++ /dev/null
@@ -1,48 +0,0 @@
-{% import 'macros/links.html' as links %}
-
-{# Associated Posts - vertical on mobile, horizontal with arrows on desktop #}
-
- {% from 'macros/scrolling_menu.html' import scrolling_menu with context %}
- {% call(entry_post) scrolling_menu('entry-posts-container', entry_posts) %}
-
- {% if entry_post.feature_image %}
-
- {% else %}
-
- {% endif %}
-
-
{{ entry_post.title }}
-
-
- {% endcall %}
-
-
-{% if container_nav_widgets %}
- {% for wdata in container_nav_widgets %}
- {% with ctx=wdata.ctx %}
- {% include wdata.widget.template with context %}
- {% endwith %}
- {% endfor %}
-{% endif %}
-
-{# Admin link #}
-{% if g.rights.admin %}
-
- {% from 'macros/admin_nav.html' import admin_nav_item %}
- {{admin_nav_item(
- url_for(
- 'calendars.calendar.day.calendar_entries.calendar_entry.admin.admin',
- slug=post.slug,
- calendar_slug=calendar.slug,
- day=day,
- month=month,
- year=year,
- entry_id=entry.id
- )
- )}}
-{% endif %}
diff --git a/browser/templates/_types/entry/_options.html b/browser/templates/_types/entry/_options.html
deleted file mode 100644
index 6c42952..0000000
--- a/browser/templates/_types/entry/_options.html
+++ /dev/null
@@ -1,98 +0,0 @@
-
- {% if entry.state == 'provisional' %}
-
-
-
-
- confirm
-
-
-
-
-
-
- decline
-
-
- {% endif %}
- {% if entry.state == 'confirmed' %}
-
-
-
-
-
- provisional
-
-
- {% endif %}
-
\ No newline at end of file
diff --git a/browser/templates/_types/entry/_posts.html b/browser/templates/_types/entry/_posts.html
deleted file mode 100644
index 9a88936..0000000
--- a/browser/templates/_types/entry/_posts.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
- {% if entry_posts %}
-
- {% for entry_post in entry_posts %}
-
- {% if entry_post.feature_image %}
-
- {% else %}
-
- {% endif %}
-
{{ entry_post.title }}
-
- Remove
-
-
- {% endfor %}
-
- {% else %}
-
No posts associated
- {% endif %}
-
-
-
-
diff --git a/browser/templates/_types/entry/_tickets.html b/browser/templates/_types/entry/_tickets.html
deleted file mode 100644
index ee5dc38..0000000
--- a/browser/templates/_types/entry/_tickets.html
+++ /dev/null
@@ -1,105 +0,0 @@
-{% if entry.ticket_price is not none %}
- {# Tickets are configured #}
-
-
- Price:
-
- £{{ ('%.2f'|format(entry.ticket_price)) }}
-
-
-
- Available:
-
- {% if entry.ticket_count is not none %}
- {{ entry.ticket_count }} tickets
- {% else %}
- Unlimited
- {% endif %}
-
-
-
- Edit ticket config
-
-
-{% else %}
- {# No tickets configured #}
-
- No tickets configured
-
- Configure tickets
-
-
-{% endif %}
-
-{# Ticket configuration form (hidden by default) #}
-
-
-
-
- Ticket Price (£)
-
-
-
-
-
-
- Total Tickets
-
-
-
-
-
-
- Save
-
-
- Cancel
-
-
-
diff --git a/browser/templates/_types/entry/admin/_nav.html b/browser/templates/_types/entry/admin/_nav.html
deleted file mode 100644
index ff658d3..0000000
--- a/browser/templates/_types/entry/admin/_nav.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% call links.link(
- url_for(
- 'calendars.calendar.day.calendar_entries.calendar_entry.ticket_types.get',
- slug=post.slug,
- calendar_slug=calendar.slug,
- entry_id=entry.id,
- year=year,
- month=month,
- day=day
- ),
- hx_select_search,
- select_colours,
- True,
- aclass=styles.nav_button,
-)%}
- ticket_types
-{% endcall %}
diff --git a/browser/templates/_types/entry/admin/header/_header.html b/browser/templates/_types/entry/admin/header/_header.html
deleted file mode 100644
index ea06833..0000000
--- a/browser/templates/_types/entry/admin/header/_header.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='entry-admin-row', oob=oob) %}
- {% call links.link(
- url_for(
- 'calendars.calendar.day.calendar_entries.calendar_entry.admin.admin',
- slug=post.slug,
- calendar_slug=calendar.slug,
- day=day,
- month=month,
- year=year,
- entry_id=entry.id
- ),
- hx_select_search
- ) %}
- {{ links.admin() }}
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/entry/admin/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
\ No newline at end of file
diff --git a/browser/templates/_types/entry/header/_header.html b/browser/templates/_types/entry/header/_header.html
deleted file mode 100644
index 6d0680c..0000000
--- a/browser/templates/_types/entry/header/_header.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='entry-row', oob=oob) %}
- {% call links.link(
- url_for(
- 'calendars.calendar.day.calendar_entries.calendar_entry.get',
- slug=post.slug,
- calendar_slug=calendar.slug,
- day=day,
- month=month,
- year=year,
- entry_id=entry.id
- ),
- hx_select_search,
- ) %}
-
- {% include '_types/entry/_title.html' %}
- {% include '_types/entry/_times.html' %}
-
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/entry/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
-
-
-
diff --git a/browser/templates/_types/entry/index.html b/browser/templates/_types/entry/index.html
deleted file mode 100644
index a980f46..0000000
--- a/browser/templates/_types/entry/index.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends '_types/day/index.html' %}
-
-{% block day_header_child %}
- {% from '_types/root/_n/macros.html' import index_row with context %}
- {% call index_row('entry-header-child', '_types/entry/header/_header.html') %}
- {% block entry_header_child %}
- {% endblock %}
- {% endcall %}
-{% endblock %}
-
-
-{% block _main_mobile_menu %}
- {% include '_types/entry/_nav.html' %}
-{% endblock %}
-
-
-
-{% block content %}
-{% include '_types/entry/_main_panel.html' %}
-{% endblock %}
\ No newline at end of file
diff --git a/browser/templates/_types/market/_oob_elements.html b/browser/templates/_types/market/_oob_elements.html
deleted file mode 100644
index b37eea0..0000000
--- a/browser/templates/_types/market/_oob_elements.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends 'oob_elements.html' %}
-
-{# OOB elements for HTMX navigation - all elements that need updating #}
-
-{# Import shared OOB macros #}
-{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
-{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
-
-{# Header with app title - includes cart-mini, navigation, and market-specific header #}
-
-{% block oobs %}
-
- {% from '_types/root/_n/macros.html' import oob_header with context %}
- {{oob_header('root-header-child', 'market-header-child', '_types/market/header/_header.html')}}
-
- {% from '_types/root/header/_header.html' import header_row with context %}
- {{ header_row(oob=True) }}
-{% endblock %}
-
-
-{% block mobile_menu %}
- {% include '_types/market/mobile/_nav_panel.html' %}
-{% endblock %}
-
-
-{% block content %}
- {% include "_types/market/_main_panel.html" %}
-{% endblock %}
-
-
diff --git a/browser/templates/_types/market/index.html b/browser/templates/_types/market/index.html
deleted file mode 100644
index df1ec4c..0000000
--- a/browser/templates/_types/market/index.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% extends '_types/root/_index.html' %}
-
-
-{% block root_header_child %}
- {% from '_types/root/_n/macros.html' import index_row with context %}
- {% call index_row('market-header-child', '_types/market/header/_header.html') %}
- {% block market_header_child %}
- {% endblock %}
- {% endcall %}
-{% endblock %}
-
-
-{% block _main_mobile_menu %}
- {% include '_types/market/mobile/_nav_panel.html' %}
-{% endblock %}
-
-
-
-{% block aside %}
-{# No aside on landing page #}
-{% endblock %}
-
-{% block content %}
- {% include "_types/market/_main_panel.html" %}
-{% endblock %}
diff --git a/browser/templates/_types/post/_meta.html b/browser/templates/_types/post/_meta.html
deleted file mode 100644
index fc752d4..0000000
--- a/browser/templates/_types/post/_meta.html
+++ /dev/null
@@ -1,128 +0,0 @@
-{# --- social/meta_post.html --- #}
-{# Context expected:
- site, post, request
-#}
-{% if post is not defined %}
- {% include 'social/meta_base.html' %}
-{% else %}
-
-{# Visibility → robots #}
-{% set is_public = (post.visibility == 'public') %}
-{% set is_published = (post.status == 'published') %}
-{% set robots_here = 'index,follow' if (is_public and is_published and not post.email_only) else 'noindex,nofollow' %}
-
-{# Compute canonical early so both this file and base can use it #}
-{% set _site_url = site().url.rstrip('/') if site and site().url else '' %}
-{% set _post_path = request.path if request else ('/posts/' ~ (post.slug or post.uuid)) %}
-{% set canonical = post.canonical_url or (_site_url ~ _post_path if _site_url else (request.url if request else None)) %}
-
-{# Include common base (charset, viewport, robots default, RSS, Org/WebSite JSON-LD) #}
-{% set robots_override = robots_here %}
-{% include 'social/meta_base.html' %}
-
-{# ---- Titles / descriptions ---- #}
-{% set og_title = post.og_title or base_title %}
-{% set tw_title = post.twitter_title or base_title %}
-
-{# Description best-effort, trimmed #}
-{% set desc_source = post.meta_description
- or post.og_description
- or post.twitter_description
- or post.custom_excerpt
- or post.excerpt
- or (post.plaintext if post.plaintext else (post.html|striptags if post.html else '')) %}
-{% set description = (desc_source|trim|replace('\n',' ')|replace('\r',' ')|striptags)|truncate(160, True, '…') %}
-
-{# Image priority #}
-{% set image_url = post.og_image
- or post.twitter_image
- or post.feature_image
- or (site().default_image if site and site().default_image else None) %}
-
-{# Dates #}
-{% set published_iso = post.published_at.isoformat() if post.published_at else None %}
-{% set updated_iso = post.updated_at.isoformat() if post.updated_at
- else (post.created_at.isoformat() if post.created_at else None) %}
-
-{# Authors / tags #}
-{% set primary_author = post.primary_author %}
-{% set authors = post.authors or ([primary_author] if primary_author else []) %}
-{% set tag_names = (post.tags or []) | map(attribute='name') | list %}
-{% set is_article = not post.is_page %}
-
-{{ base_title }}
-
-{% if canonical %} {% endif %}
-
-{# ---- Open Graph ---- #}
-
-
-
-
-{% if canonical %} {% endif %}
-{% if image_url %} {% endif %}
-{% if is_article and published_iso %} {% endif %}
-{% if is_article and updated_iso %}
-
-
-{% endif %}
-{% if is_article and post.primary_tag and post.primary_tag.name %}
-
-{% endif %}
-{% if is_article %}
- {% for t in tag_names %}
-
- {% endfor %}
-{% endif %}
-
-{# ---- Twitter ---- #}
-
-{% if site and site().twitter_site %} {% endif %}
-{% if primary_author and primary_author.twitter %}
-
-{% endif %}
-
-
-{% if image_url %} {% endif %}
-
-{# ---- JSON-LD author value (no list comprehensions) ---- #}
-{% if authors and authors|length == 1 %}
- {% set author_value = {"@type": "Person", "name": authors[0].name} %}
-{% elif authors %}
- {% set ns = namespace(arr=[]) %}
- {% for a in authors %}
- {% set _ = ns.arr.append({"@type": "Person", "name": a.name}) %}
- {% endfor %}
- {% set author_value = ns.arr %}
-{% else %}
- {% set author_value = none %}
-{% endif %}
-
-{# ---- JSON-LD using combine for optionals ---- #}
-{% set jsonld = {
- "@context": "https://schema.org",
- "@type": "BlogPosting" if is_article else "WebPage",
- "mainEntityOfPage": canonical,
- "headline": base_title,
- "description": description,
- "image": image_url,
- "datePublished": published_iso,
- "author": author_value,
- "publisher": {
- "@type": "Organization",
- "name": site().title if site and site().title else "",
- "logo": {"@type": "ImageObject", "url": site().logo if site and site().logo else image_url}
- }
-} %}
-
-{% if updated_iso %}
- {% set jsonld = jsonld | combine({"dateModified": updated_iso}) %}
-{% endif %}
-{% if tag_names %}
- {% set jsonld = jsonld | combine({"keywords": tag_names | join(", ")}) %}
-{% endif %}
-
-
-{% endif %}
diff --git a/browser/templates/_types/post/_nav.html b/browser/templates/_types/post/_nav.html
deleted file mode 100644
index db8cdc4..0000000
--- a/browser/templates/_types/post/_nav.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% import 'macros/links.html' as links %}
- {# Widget-driven container nav — entries, calendars, markets #}
- {% if container_nav_widgets %}
-
- {% include '_types/post/admin/_nav_entries.html' %}
-
- {% endif %}
diff --git a/browser/templates/_types/post/admin/_nav.html b/browser/templates/_types/post/admin/_nav.html
deleted file mode 100644
index 7296d15..0000000
--- a/browser/templates/_types/post/admin/_nav.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% import 'macros/links.html' as links %}
-
-{% call links.link(url_for('blog.post.admin.entries', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
- entries
-{% endcall %}
-{% call links.link(url_for('blog.post.admin.data', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
- data
-{% endcall %}
-{% call links.link(url_for('blog.post.admin.edit', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
- edit
-{% endcall %}
-{% call links.link(url_for('blog.post.admin.settings', slug=post.slug), hx_select_search, select_colours, True, aclass=styles.nav_button) %}
- settings
-{% endcall %}
\ No newline at end of file
diff --git a/browser/templates/_types/post/admin/_oob_elements.html b/browser/templates/_types/post/admin/_oob_elements.html
deleted file mode 100644
index 4bd3b74..0000000
--- a/browser/templates/_types/post/admin/_oob_elements.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "oob_elements.html" %}
-{# OOB elements for post admin page #}
-
-{# Import shared OOB macros #}
-{% from '_types/root/header/_oob.html' import root_header_start, root_header_end with context %}
-{% from '_types/root/_oob_menu.html' import mobile_menu with context %}
-
-{% block oobs %}
- {% from '_types/root/_n/macros.html' import oob_header with context %}
- {{oob_header('post-header-child', 'post-admin-header-child', '_types/post/admin/header/_header.html')}}
-
- {% from '_types/post/header/_header.html' import header_row with context %}
- {{ header_row(oob=True) }}
-{% endblock %}
-
-{% block mobile_menu %}
- {% include '_types/post/admin/_nav.html' %}
-{% endblock %}
-
-{% block content %}
-nowt
-{% endblock %}
\ No newline at end of file
diff --git a/browser/templates/_types/post/admin/index.html b/browser/templates/_types/post/admin/index.html
deleted file mode 100644
index fb1de5f..0000000
--- a/browser/templates/_types/post/admin/index.html
+++ /dev/null
@@ -1,18 +0,0 @@
-{% extends '_types/post/index.html' %}
-{% import 'macros/layout.html' as layout %}
-
-{% block post_header_child %}
- {% from '_types/root/_n/macros.html' import index_row with context %}
- {% call index_row('post-admin-header-child', '_types/post/admin/header/_header.html') %}
- {% block post_admin_header_child %}
- {% endblock %}
- {% endcall %}
-{% endblock %}
-
-{% block _main_mobile_menu %}
- {% include '_types/post/admin/_nav.html' %}
-{% endblock %}
-
-{% block content %}
-nowt
-{% endblock %}
diff --git a/browser/templates/_types/post/header/_header.html b/browser/templates/_types/post/header/_header.html
deleted file mode 100644
index a75eda3..0000000
--- a/browser/templates/_types/post/header/_header.html
+++ /dev/null
@@ -1,19 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='post-row', oob=oob) %}
-
- {% if post.feature_image %}
-
- {% endif %}
-
- {{ post.title | truncate(160, True, '…') }}
-
-
- {% call links.desktop_nav() %}
- {% include '_types/post/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
\ No newline at end of file
diff --git a/browser/templates/_types/post_entries/_main_panel.html b/browser/templates/_types/post_entries/_main_panel.html
deleted file mode 100644
index 342041e..0000000
--- a/browser/templates/_types/post_entries/_main_panel.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
- {# Associated Entries List #}
- {% include '_types/post/admin/_associated_entries.html' %}
-
- {# Calendars Browser #}
-
-
Browse Calendars
- {% for calendar in all_calendars %}
-
-
- {% if calendar.post.feature_image %}
-
- {% else %}
-
- {% endif %}
-
-
-
- {{ calendar.name }}
-
-
- {{ calendar.post.title }}
-
-
-
-
-
- {% else %}
-
No calendars found.
- {% endfor %}
-
-
\ No newline at end of file
diff --git a/browser/templates/_types/post_entries/header/_header.html b/browser/templates/_types/post_entries/header/_header.html
deleted file mode 100644
index 019c000..0000000
--- a/browser/templates/_types/post_entries/header/_header.html
+++ /dev/null
@@ -1,17 +0,0 @@
-{% import 'macros/links.html' as links %}
-{% macro header_row(oob=False) %}
- {% call links.menu_row(id='post_entries-row', oob=oob) %}
- {% call links.link(url_for('blog.post.admin.entries', slug=post.slug), hx_select_search) %}
-
-
- entries
-
- {% endcall %}
- {% call links.desktop_nav() %}
- {% include '_types/post_entries/_nav.html' %}
- {% endcall %}
- {% endcall %}
-{% endmacro %}
-
-
-
diff --git a/browser/templates/_types/product/_cart.html b/browser/templates/_types/product/_cart.html
deleted file mode 100644
index 4947d86..0000000
--- a/browser/templates/_types/product/_cart.html
+++ /dev/null
@@ -1,278 +0,0 @@
-{% macro add(slug, cart, oob='false') %}
-{% set quantity = cart
- | selectattr('product.slug', 'equalto', slug)
- | sum(attribute='quantity') %}
-
-
-
- {% if not quantity %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% else %}
-
- {% endif %}
-
-{% endmacro %}
-
-
-
-{% macro cart_item(oob=False) %}
-
-{% set p = item.product %}
-{% set unit_price = p.special_price or p.regular_price %}
-
-
- {% if p.image %}
-
- {% else %}
-
- No image
-
'market', 'product', p.slug
- {% endif %}
-
-
- {# Details #}
-
-
-
-
- {% set href=market_product_url(p.slug, market_place=item.market_place) %}
-
- {{ p.title }}
-
-
-
- {% if p.brand %}
-
- {{ p.brand }}
-
- {% endif %}
-
- {% if item.is_deleted %}
-
-
- This item is no longer available or price has changed
-
- {% endif %}
-
-
- {# Unit price #}
-
- {% if unit_price %}
- {% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
-
- {{ symbol }}{{ "%.2f"|format(unit_price) }}
-
- {% if p.special_price and p.special_price != p.regular_price %}
-
- {{ symbol }}{{ "%.2f"|format(p.regular_price) }}
-
- {% endif %}
- {% else %}
-
No price
- {% endif %}
-
-
-
-
-
- Quantity
- {% set qty_url = cart_quantity_url(item.product_id) if cart_quantity_url is defined else market_product_url(p.slug, 'cart', item.market_place) %}
-
-
-
-
- -
-
-
-
- {{ item.quantity }}
-
-
-
-
-
- +
-
-
-
- {% if cart_delete_url is defined %}
-
-
-
-
-
-
- {% endif %}
-
-
-
- {% if unit_price %}
- {% set line_total = unit_price * item.quantity %}
- {% set symbol = "£" if p.regular_price_currency == "GBP" else p.regular_price_currency %}
-
- Line total:
- {{ symbol }}{{ "%.2f"|format(line_total) }}
-
- {% endif %}
-
-
-
-
-
-{% endmacro %}
diff --git a/models/domain_event.py b/models/domain_event.py
deleted file mode 100644
index 1fae664..0000000
--- a/models/domain_event.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from __future__ import annotations
-
-from datetime import datetime
-from sqlalchemy import String, Integer, DateTime, Text, func
-from sqlalchemy.dialects.postgresql import JSONB
-from sqlalchemy.orm import Mapped, mapped_column
-from shared.db.base import Base
-
-
-class DomainEvent(Base):
- __tablename__ = "domain_events"
-
- id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
- event_type: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
- aggregate_type: Mapped[str] = mapped_column(String(64), nullable=False)
- aggregate_id: Mapped[int] = mapped_column(Integer, nullable=False)
- payload: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
- state: Mapped[str] = mapped_column(
- String(20), nullable=False, default="pending", server_default="pending", index=True
- )
- attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
- max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=5, server_default="5")
- last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
- created_at: Mapped[datetime] = mapped_column(
- DateTime(timezone=True), nullable=False, server_default=func.now()
- )
- processed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
-
- def __repr__(self) -> str:
- return f""