All client apps (including federation) now redirect to account for OAuth.
Factory excludes account from OAuth client blueprint registration.
SSO logout chains through account instead of federation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Federation sets sso_hint=1 on .rose-ash.com after magic link login
- Client apps: before_request checks sso_hint, triggers silent OAuth
once per session (sso_checked flag prevents loops)
- Logout clears sso_hint cookie on all apps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Account's / requires login, so redirecting there after logout
triggers silent OAuth re-authentication. Blog home is safe.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Nav sign-in links point to account_url('/') instead of login_url()
- After-request hook clears old blog_session cookie on .rose-ash.com
(prevents collision with new per-app first-party cookies)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The federation auth blueprint is mounted at /auth, so the authorize
endpoint is /auth/oauth/authorize, not /oauth/authorize.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Each app's EventProcessor now filters by origin_app so apps don't steal
each other's pending activities. emit_activity() and publish_activity()
auto-detect the app name from Quart's current_app.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When login_url() is called from a different app (e.g. cart), the
anonymous cart_sid is in that app's session cookie. Pass it as a
query parameter so the auth app can store it and use it for adoption.
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>
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>
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>
- 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>
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>
- 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>
shared/logging/ shadows Python's stdlib logging module, causing a
circular import when any code does `import logging`. This breaks
both the entrypoint Redis flush and Hypercorn app loading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>