Mastodon verifies the object id domain matches the actor domain.
Using the post URL (coop.rose-ash.com) as object id caused silent
rejection. Now always uses {activity_id}/object on federation domain.
Also adds to/cc on object for visibility determination.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Federation activities are now created at write time via try_publish()
instead of relying on async event handlers. Fixes race condition where
multiple EventProcessors could consume post.published events in apps
that couldn't meaningfully process them.
AP delivery (federation.activity_created → inbox POST) stays async.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use .scalars().first() + LIMIT 1 instead of scalar_one_or_none()
which crashes when multiple activities exist for the same source
- Allow re-Create after Delete (re-publish after unpublish)
- Add missing on_post_unpublished handler to root shared copy
- Sync add_follower upsert fix to root shared
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- on_post_unpublished handler sends Delete/Tombstone activity
- Create/Update objects use post URL as id (for Delete reference)
- Delete objects use Tombstone type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Edit: placeholder "Page title..." vs "Post title..."
- Settings: slug placeholder, featured checkbox, custom template
all say "page" when is_page is true
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pages are container/landing pages, not blog posts. Hide the like
button, tags/authors bar, and excerpt when post.is_page is true.
Feature image and content still render for both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The adoption logic soft-deleted ALL user entries before adopting
anonymous session entries. This nuked confirmed/ordered bookings
every time the user logged in. Add state="pending" filter so only
stale pending entries are cleared.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 3-5 of ActivityPub integration:
- Federation handlers: post.published, calendar_entry.created, product.listed
→ publish_activity() for AP outbox
- AP delivery handler: federation.activity_created → sign + POST to follower
inboxes with HTTP Signatures
- IPFS storage wired into publish_activity() (best-effort)
- Anchoring utility: merkle trees + OpenTimestamps Bitcoin timestamping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the macro is imported without context ({% from ... import mini %}),
template variables like cart_count aren't visible. The new count param
allows callers to pass it explicitly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ticket_type_id field to TicketDTO for grouping
- Add adjust_ticket_quantity to CalendarService protocol + SQL impl
- Add stub for adjust_ticket_quantity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reserved tickets now flow through the cart and checkout pipeline:
- TicketDTO gains price, entry_id, order_id, calendar_container_id
- CartSummaryDTO gains ticket_count, ticket_total
- 6 new CalendarService methods for ticket lifecycle
- cart_summary includes tickets; login adoption migrates tickets
- New _ticket_items.html template for checkout return page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduces a widget system where domains register UI fragments into
named slots (container_nav, container_card, account_page, account_link).
Host apps iterate widgets generically without naming any domain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add TicketDTO, user_tickets/user_bookings to CalendarService protocol
and SqlCalendarService implementation, plus nav links and panel
templates for the auth account sub-pages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use top_slug/sub_slug directly instead of current_local_href for
active state detection. The previous approach compared full request
paths against short category-relative paths, which never matched.
This also avoids conflicting with current_local_href used by brand
filter URL construction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
select_colours was only defined via {% set %} in the root header
template, making it undefined in market/browse nav templates. Moving
it to a Jinja global ensures aria-selected styling works everywhere.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added app_name Jinja global and aria-selected to nav menu links.
Matches by first path segment (e.g. /market/... → "market") or by
app_name for cross-domain cases (e.g. cart app → "cart" menu item).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Minus button now floors at 0 instead of going negative. A trash button
with SweetAlert2 confirmation appears when cart_delete_url is defined
(cart app only). Items at quantity 0 remain visible for re-increment.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The cart_item macro now checks for cart_quantity_url (defined only in the cart app)
and uses it for same-origin quantity updates, falling back to market_product_url.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add create_marketplace() and soft_delete_marketplace() to MarketService
protocol, SQL implementation, and stubs — centralises market CRUD that
was previously duplicated in blog and events app-level service files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Templates were accessing entry.calendar.name/slug/post via ORM relationships,
but these entries are now CalendarEntryDTOs. Use flat fields instead
(calendar_name, calendar_slug, etc.).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Prevents SQLAlchemy 'table already defined' error if the table gets
registered by a stale glue submodule or cached Docker layer before
the shared model is loaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MenuNode and ContainerRelation now live in shared/models/ — importing
glue.models caused SQLAlchemy to see duplicate table definitions.
Also register the two new models in shared/models/__init__.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add typed service contracts (Protocols + frozen DTOs) in shared/contracts/
for cross-domain communication. Each domain exposes a service interface
(BlogService, CalendarService, MarketService, CartService) backed by SQL
implementations in shared/services/. A singleton registry with has() guards
enables composable startup — apps register their own domain service and
stubs for absent domains.
Absorbs glue layer: navigation, relationships, event handlers (login,
container, order) now live in shared/ with has()-guarded service calls.
Factory gains domain_services_fn parameter for per-app service registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CalendarEntry.ticket_types used default lazy loading which triggers
MissingGreenlet in async context when accessed in templates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The |qs filter returns "" when makeqs_factory is not set (events app),
causing |host to generate the events app root URL which has no route.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move 6 model files to shared/models/ (ghost_content, order, page_config,
market, market_place, calendars) so apps import from shared, not each other
- Fix auth templates: replace url_for('auth.*') with coop_url() for
cross-app compatibility
- Fix TYPE_CHECKING import in sumup.py to use shared.models.order
- Delete dead infrastructure/cart_loader.py (inverted dependency)
- Update models/__init__.py to export all new models
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The orders link navigates cross-domain (cart subdomain from coop) and
htmx 2.0 selfRequestsOnly blocks cross-origin requests. Explicitly
disable htmx on this link so the browser does a normal navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
url_for('auth.account') and url_for('auth.newsletters') only work in
the blog app. Other apps (cart) that render these shared templates
don't have an auth blueprint. Use coop_url() so links always resolve
to the blog subdomain regardless of which app renders the template.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The market menu item in the top bar should link to the blog page at
coop_url('/market/'), not to market_url('/') which goes to the market
subdomain. Reverts the incorrect 'market' addition to _app_slugs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
market_url('/product/...') was missing the /<page_slug>/<market_slug>/
prefix required by the market app's route structure. New helper
market_product_url() resolves the prefix from request context
(g.post_slug/g.market_slug in market app, g.page_slug + market_place
in cart app).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Accept optional page_config parameter to use per-page
SumUp merchant code and API key instead of global config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_join_url_parts() only checked the first segment for a scheme, so
passing an already-absolute URL (e.g. from cart_url()) through the
|host filter would join route_prefix() + absolute URL, producing
"https://host/https://host/path/".
Now detects schemes in later segments and resets the base.
Also add missing 'market' entry to _nav.html _app_slugs to match
_nav_oob.html — without it the market menu item fell through to
coop_url('/market/') instead of market_url('/').
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The checkout form action used url_for('cart_global.checkout') which
only resolves inside the cart app. Replace with cart_url() and
page_cart_url() Jinja globals that work across all apps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge three alembic heads and drop:
- calendar_entries.order_id FK → orders.id
- tickets.order_id FK → orders.id
Columns kept as plain integers for glue-service bridging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The back_populates="menu_items" referenced a relationship removed from
Post in the glue layer commit. MenuItem model kept for table preservation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Templates: item.post.X → item.X (MenuNode has label/slug/feature_image directly)
- factory.py: add glue.models to import loop + register_glue_handlers() at startup
- alembic env.py: add glue.models to import loop
- New migration: container_relations + menu_nodes tables with backfill from existing data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cross-domain relationships like Product.order_items → OrderItem use
string references that SQLAlchemy resolves by class name lookup. All
model packages must be imported so every class is registered before
mapper configuration runs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two branches diverged from e5c3f9a2b1d6 (tag_groups):
- domain_events → generic_containers (g7e5b1c3d4f8)
- tickets → page_configs → market_places → page_tracking (c3d4e5f6a7b8)
This merge migration joins them into a single head so
alembic upgrade head works without specifying a branch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>