Commit Graph

45 Commits

Author SHA1 Message Date
436848060d Merge branch 'worktree-sx-loop-cleanup' into macros
# Conflicts:
#	blog/sx/sx_components.py
#	federation/sx/profile.sx
#	federation/sx/sx_components.py
#	orders/sx/sx_components.py
2026-03-05 16:08:36 +00:00
c1ad6fd8d4 Replace Python sx_call loops with data-driven SX defcomps using map
Move rendering logic from Python for-loops building sx_call strings into
SX defcomp components that use map/lambda over data dicts. Python now
serializes display data into plain dicts and passes them via a single
sx_call; the SX layer handles iteration and conditional rendering.

Covers orders (rows, items, calendar, tickets), federation (timeline,
search, actors, profile activities), and blog (cards, pages, filters,
snippets, menu items, tag groups, page search, nav OOB).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:03:29 +00:00
1077fae815 Merge branch 'worktree-sx-layout-conversion' into macros
# Conflicts:
#	blog/sxc/pages/layouts.py
#	cart/sxc/pages/layouts.py
#	events/sxc/pages/helpers.py
#	events/sxc/pages/layouts.py
#	market/sxc/pages/layouts.py
#	sx/sxc/pages/layouts.py
2026-03-04 22:25:52 +00:00
57a31a3b83 Convert all 23 register_custom_layout calls to register_sx_layout across 6 services
Layout defcomps are now fully self-contained via IO-primitive auto-fetch
macros, eliminating Python layout functions that manually threaded context
values through SxExpr wrappers.

Services converted:
- Federation (1 layout): social
- Blog (7 layouts): blog, blog-settings, blog-cache, blog-snippets,
  blog-menu-items, blog-tag-groups, blog-tag-group-edit
- SX docs (2 layouts): sx, sx-section
- Cart (2 layouts): cart-page, cart-admin + orders/order-detail
- Events (9 layouts): calendar-admin, slots, slot, day-admin, entry,
  entry-admin, ticket-types, ticket-type, markets
- Market (2 layouts): market, market-admin

New IO primitives added to shared/sx/primitives_io.py:
- federation-actor-ctx, cart-page-ctx, request-view-args
- events-calendar-ctx, events-day-ctx, events-entry-ctx,
  events-slot-ctx, events-ticket-type-ctx
- market-header-ctx (pre-builds desktop/mobile nav as SxExpr)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 22:21:44 +00:00
278ae3e8f6 Make SxExpr a str subclass, sx_call/render functions return SxExpr
SxExpr is now a str subclass so it works everywhere a plain string
does (join, isinstance, f-strings) while serialize() still emits it
unquoted. sx_call() and all internal render functions (_render_to_sx,
async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to
wrap" bug class that caused the sx_content leak and list serialization
bugs.

- Phase 0: SxExpr(str) with .source property, __add__/__radd__
- Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged)
- Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx,
  mobile_menu_sx return SxExpr; remove isinstance(str) workaround
- Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files
- Phase 4: serialize() docstring, handler return docs, ;; returns: sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 21:47:00 +00:00
959e63d440 Remove render_to_sx from public API: enforce sx_call for all service code
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m44s
Replace ~250 render_to_sx calls across all services with sync sx_call,
converting many async functions to sync where no other awaits remained.
Make render_to_sx/render_to_sx_with_env private (_render_to_sx).
Add (post-header-ctx) IO primitive and shared post/post-admin defmacros.
Convert built-in post/post-admin layouts from Python to register_sx_layout
with .sx defcomps. Remove dead post_admin_mobile_nav_sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 19:30:45 +00:00
7fda7a8027 Replace env free-variable threading with IO-primitive auto-fetch macros
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m38s
Layout components now self-resolve context (cart-mini, auth-menu, nav-tree,
rights, URLs) via new IO primitives (root-header-ctx, select-colours,
account-nav-ctx, app-rights) and defmacro wrappers (~root-header-auto,
~auth-header-row-auto, ~root-mobile-auto). This eliminates _ctx_to_env(),
HELPER_CSS_CLASSES, and verbose :key threading across all 10 services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:20:57 +00:00
8eaf4026ab Slim sxc/pages/__init__.py for federation, test, cart, blog
Move render functions, layouts, helpers, and utils from __init__.py
to sub-modules (renders.py, layouts.py, helpers.py, utils.py).
Update all bp route imports to point at sub-modules directly.
Each __init__.py is now ≤20 lines of setup + registration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 16:51:57 +00:00
a30e7228d8 Pass all values as &key args to ~root-header/~root-mobile
Nested component calls in _aser are serialized without body expansion,
so free variables inside ~root-header would be sent unresolved to the
client. Fix by making ~root-header/~root-mobile take all values as
&key params, and having parent layout defcomps pass them explicitly.
The parent layout bodies ARE expanded (via async_eval_slot_to_sx),
so their free variables resolve correctly during that expansion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:50:14 +00:00
45c5e4a0db Add register_sx_layout infrastructure, convert account/federation/orders
Phase 0: Add _ctx_to_env() and render_to_sx_with_env() to shared/sx/helpers.py,
register_sx_layout() to shared/sx/layouts.py, and ~root-header/~root-mobile
wrapper defcomps to layout.sx. Convert built-in "root" layout to .sx.

Phases 1-3: Convert account (65→19 lines), federation (105→97 lines),
and orders (88→21 lines) to use register_sx_layout with .sx defcomps
that read ctx values as free variables from the evaluation environment.
No more Python building SX strings via SxExpr(await root_header_sx(ctx)).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 14:39:53 +00:00
418ac9424f Eliminate Python page helpers from account, federation, and cart
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m8s
All three services now fetch page data via (service ...) IO primitives
in .sx defpages instead of Python middleman functions.

- Account: newsletters-data → AccountPageService.newsletters_data
- Federation: 8 page helpers → FederationPageService methods
  (timeline, compose, search, following, followers, notifications)
- Cart: 4 page helpers → CartPageService methods
  (overview, page-cart, admin, payments)
- Serializers moved to service modules, thin delegates kept for routes
- ~520 lines of Python page helpers removed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 02:01:50 +00:00
dacb61b0ae Delete orders + federation sx_components.py — rendering inlined to routes
Phase 2 (Orders):
- Checkout error/return renders moved directly into route handlers
- Removed orphaned test_sx_helpers.py

Phase 3 (Federation):
- Auth pages use _render_social_auth_page() helper in routes
- Choose-username render inlined into identity routes
- Timeline/search/follow/interaction renders inlined into social routes
  using serializers imported from sxc.pages
- Added _social_page() to sxc/pages/__init__.py for shared use
- Home page renders inline in app.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 01:22:33 +00:00
e085fe43b4 Replace sx_call() with render_to_sx() across all services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m6s
Python no longer generates s-expression strings. All SX rendering now
goes through render_to_sx() which builds AST from native Python values
and evaluates via async_eval_to_sx() — no SX string literals in Python.

- Add render_to_sx()/render_to_html() infrastructure in shared/sx/helpers.py
- Add (abort status msg) IO primitive in shared/sx/primitives_io.py
- Convert all 9 services: ~650 sx_call() invocations replaced
- Convert shared helpers (root_header_sx, full_page_sx, etc.) to async
- Fix likes service import bug (likes.models → models)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 00:08:33 +00:00
4e5f9ff16c Remove dead render_profile_page from federation sx_components
This function was replaced by defpage-based rendering but never deleted.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:41:19 +00:00
193578ef88 Move SX construction from Python to .sx defcomps (phases 0-4)
Eliminate Python s-expression string building across account, orders,
federation, and cart services. Visual rendering logic now lives entirely
in .sx defcomp components; Python files contain only data serialization,
header/layout wiring, and thin wrappers that call defcomps.

Phase 0: Shared DRY extraction — auth/orders header defcomps, format-decimal/
pluralize/escape/route-prefix primitives.
Phase 1: Account — dashboard, newsletters, login/device/check-email content.
Phase 2: Orders — order list, detail, filter, checkout return assembled defcomps.
Phase 3: Federation — social nav, post cards, timeline, search, actors,
notifications, compose, profile assembled defcomps.
Phase 4: Cart — overview, page cart items/calendar/tickets/summary, admin,
payments assembled defcomps; orders rendering reuses Phase 2 shared defcomps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:36:34 +00:00
e30cb0a992 Auto-mount fragment handlers: eliminate fragment blueprint boilerplate across all 8 services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 16m38s
Fragment read API is now fully declarative — every handler is a defhandler
s-expression dispatched through one shared auto_mount_fragment_handlers()
function. Replaces 8 near-identical blueprint files (~35 lines each) with
a single function call per service. Events Python handlers (container-cards,
account-page) extracted to a standalone module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:13:15 +00:00
293f7713d6 Auto-mount defpages: eliminate Python route stubs across all 9 services
Some checks failed
Build and Deploy / build-and-deploy (push) Failing after 16s
Defpages are now declared with absolute paths in .sx files and auto-mounted
directly on the Quart app, removing ~850 lines of blueprint mount_pages calls,
before_request hooks, and g.* wrapper boilerplate. A new page = one defpage
declaration, nothing else.

Infrastructure:
- async_eval awaits coroutine results from callable dispatch
- auto_mount_pages() mounts all registered defpages on the app
- g._defpage_ctx pattern passes helper data to layout context

Migrated: sx, account, orders, federation, cart, market, events, blog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:03:15 +00:00
0a81a2af01 Convert social and federation profile from Jinja to SX rendering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 14m34s
Add primitives (replace, strip-tags, slice, csrf-token), convert all
social blueprint routes and federation profile to SX content builders,
delete 12 unused Jinja templates and social_lite layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 17:43:47 +00:00
544892edd9 Delete 391 dead Jinja templates replaced by sx_components/defpage
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m13s
All app-level templates have been replaced by native sx component builders
and defpage declarative routes. Removes ~15,200 lines of dead HTML.

Kept: shared/browser templates (errors, ap_social, macros, root layout),
account + federation _email/magic_link, federation profile.html chain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:10:56 +00:00
c243d17eeb Migrate all apps to defpage declarative page routes
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m41s
Replace Python GET page handlers with declarative defpage definitions in .sx
files across all 8 apps (sx docs, orders, account, market, cart, federation,
events, blog). Each app now has sxc/pages/ with setup functions, layout
registrations, page helpers, and .sx defpage declarations.

Core infrastructure: add g I/O primitive, PageDef support for auth/layout/
data/content/filter/aside/menu slots, post_author auth level, and custom
layout registration. Remove ~1400 lines of render_*_page/render_*_oob
boilerplate. Update all endpoint references in routes, sx_components, and
templates to defpage_* naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:52:34 +00:00
ab75e505a8 Add macros, declarative handlers (defhandler), and convert all fragment routes to sx
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Phase 1 — Macros: defmacro + quasiquote syntax (`, ,, ,@) in parser,
evaluator, HTML renderer, and JS mirror. Macro type, expansion, and
round-trip serialization.

Phase 2 — Expanded primitives: app-url, url-for, asset-url, config,
format-date, parse-int (pure); service, request-arg, request-path,
nav-tree, get-children (I/O); jinja-global, relations-from (pure).
Updated _io_service to accept (service "registry-name" "method" :kwargs)
with auto kebab→snake conversion. DTO-to-dict now expands datetime fields
into year/month/day convenience keys. Tuple returns converted to lists.

Phase 3 — Declarative handlers: HandlerDef type, defhandler special form,
handler registry (service → name → HandlerDef), async evaluator+renderer
(async_eval.py) that awaits I/O primitives inline within control flow.
Handler loading from .sx files, execute_handler, blueprint factory.

Phase 4 — Convert all fragment routes: 13 Python fragment handlers across
8 services replaced with declarative .sx handler files. All routes.py
simplified to uniform sx dispatch pattern. Two Jinja HTML handlers
(events/container-cards, events/account-page) kept as Python.

New files: shared/sx/async_eval.py, shared/sx/handlers.py,
shared/sx/tests/test_handlers.py, plus 13 handler .sx files under
{service}/sx/handlers/. MarketService.product_by_slug() added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:22:18 +00:00
c0d369eb8e Refactor SX templates: shared components, Python migration, cleanup
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 6m0s
- Extract shared components (empty-state, delete-btn, sentinel, crud-*,
  view-toggle, img-or-placeholder, avatar, sumup-settings-form, auth
  forms, order tables/detail/checkout)
- Migrate all Python sx_call() callers to use shared components directly
- Remove 55+ thin wrapper defcomps from domain .sx files
- Remove trivial passthrough wrappers (blog-header-label, market-card-text, etc)
- Unify duplicate auth flows (account + federation) into shared/sx/templates/auth.sx
- Unify duplicate order views (cart + orders) into shared/sx/templates/orders.sx
- Disable static file caching in dev (SEND_FILE_MAX_AGE_DEFAULT=0)
- Add SX response validation and debug headers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:34:34 +00:00
e8bc228c7f Rebrand sexp → sx across web platform (173 files)
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m37s
Rename all sexp directories, files, identifiers, and references to sx.
artdag/ excluded (separate media processing DSL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:06:57 +00:00
a643b3532d Phase 5 cleanup: remove legacy HTML components, fix nav-tree fragment
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m43s
- Remove old raw! layout components (~app-head, ~app-layout, ~oob-response,
  ~header-row, ~menu-row, ~oob-header, ~header-child) from layout.sexp
- Convert nav-tree fragment from Jinja HTML to sexp source, fixing the
  "Unexpected character: ." parse error caused by HTML leaking into sexp
- Add _as_sexp() helper to safely coerce HTML fragments to ~rich-text
- Fix federation/sexp/search.sexpr extra closing paren
- Remove dead _html() wrappers from blog and account sexp_components
- Remove stale render import from cart sexp_components
- Add dev_watcher.py to auto-reload on .sexp/.sexpr/.js/.css changes
- Add test_parse_all.py to parse-check all 59 sexpr/sexp files
- Fix test assertions for sx- attribute prefix (was hx-)
- Add sexp.js version logging for cache debugging

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 10:12:03 +00:00
22802bd36b Send all responses as sexp wire format with client-side rendering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m35s
- Server sends sexp source text, client (sexp.js) renders everything
- SexpExpr marker class for nested sexp composition in serialize()
- sexp_page() HTML shell with data-mount="body" for full page loads
- sexp_response() returns text/sexp for OOB/partial responses
- ~app-body layout component replaces ~app-layout (no raw!)
- ~rich-text is the only component using raw! (for CMS HTML content)
- Fragment endpoints return text/sexp, auto-wrapped in SexpExpr
- All _*_html() helpers converted to _*_sexp() returning sexp source
- Head auto-hoist: sexp.js moves meta/title/link/script[ld+json]
  from rendered body to document.head automatically
- Unknown components render warning box instead of crashing page
- Component kwargs preserve AST for lazy rendering (fixes <> in kwargs)
- Fix unterminated paren in events/sexp/tickets.sexpr

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 09:45:07 +00:00
3809affcab Test dashboard: full menu system, all-service tests, filtering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m11s
- Run tests for all 10 services via per-service pytest subprocesses
- Group results by service with section headers
- Clickable summary cards filter by outcome (passed/failed/errors/skipped)
- Service filter nav using ~nav-link buttons in menu bar
- Full menu integration: ~header-row + ~header-child + ~menu-row
- Show logo image via cart-mini rendering
- Mount full service directories in docker-compose for test access
- Add 24 unit test files across 9 services

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 22:54:25 +00:00
f9d9697c67 Externalize sexp to .sexpr files + render() API
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m20s
Replace all 676 inline sexp() string calls across 7 services with
render(component_name, **kwargs) calls backed by 46 external .sexpr
component definition files (587 defcomps total).

- Add render() function to shared/sexp/jinja_bridge.py
- Add load_service_components() helper and update load_sexp_dir() for *.sexpr
- Update parser keyword regex to support HTMX hx-on::event syntax
- Convert remaining inline HTML in route files to render() calls
- Add shared/sexp/templates/misc.sexp for cross-service utility components

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 16:14:58 +00:00
0d1ce92e52 Fix sexp parse errors: avoid literal parentheses in sexp string args
The sexp parser doesn't handle "(" and ")" as string literals
inside expressions. Use raw! with pre-formatted strings instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:20:41 +00:00
09b5a5b4f6 Convert account, orders, and federation sexp_components.py to pure sexp() calls
Eliminates all f-string HTML from the remaining three services,
completing the migration of all sexp_components.py files to the
s-expression rendering system.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:15:17 +00:00
53c4a0a1e0 Externalize sexp component templates and delete redundant HTML fragments
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m13s
Move 24 defcomp definitions from Python string constants in components.py
to 7 grouped .sexp files under shared/sexp/templates/. Add load_sexp_dir()
to jinja_bridge.py for file-based loading. Migrate events and market
link-card fragment handlers from render_template to sexp. Delete 9
superseded Jinja HTML fragment templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 08:55:54 +00:00
838ec982eb Phase 7: Replace render_template() with s-expression rendering in all POST/PUT/DELETE routes
Eliminates all render_template() calls from POST/PUT/DELETE handlers across
all 7 services. Moves sexp_components.py into sexp/ packages per service.

- Blog: like toggle, snippets, cache clear, features/sumup/entry panels,
  create/delete market, WYSIWYG editor panel (render_editor_panel)
- Federation: like/unlike/boost/unboost, follow/unfollow, actor card,
  interaction buttons
- Events: ticket widget, checkin, confirm/decline/provisional, tickets
  config, posts CRUD, description edit/save, calendar/slot/ticket_type
  CRUD, payments, buy tickets, day main panel, entry page
- Market: like toggle, cart add response
- Account: newsletter toggle
- Cart: checkout error pages (3 handlers)
- Orders: checkout error page (1 handler)

Remaining render_template() calls are exclusively in GET handlers and
internal services (email templates, fragment endpoints).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:15:29 +00:00
d53b9648a9 Phase 6: Replace render_template() with s-expression rendering in all GET routes
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m15s
Migrate ~52 GET route handlers across all 7 services from Jinja
render_template() to s-expression component rendering. Each service
gets a sexp_components.py with page/oob/cards render functions.

- Add per-service sexp_components.py (account, blog, cart, events,
  federation, market, orders) with full page, OOB, and pagination
  card rendering
- Add shared/sexp/helpers.py with call_url, root_header_html,
  full_page, oob_page utilities
- Update all GET routes to use get_template_context() + render fns
- Fix get_template_context() to inject Jinja globals (URL helpers)
- Add qs_filter to base_context for sexp filter URL building
- Mount sexp_components.py in docker-compose.dev.yml for all services
- Import sexp_components in app.py for Hypercorn --reload watching
- Fix route_prefix import (shared.utils not shared.infrastructure.urls)
- Fix federation choose-username missing actor in context
- Fix market page_markets missing post in context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:19:33 +00:00
04419a1ec6 Switch federation link-card fragment to sexp rendering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m2s
All four services (blog, market, events, federation) now use the shared
~link-card s-expression component instead of per-service Jinja templates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 14:59:25 +00:00
fa431ee13e Split cart into 4 microservices: relations, likes, orders, page-config→blog
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Phase 1 - Relations service (internal): owns ContainerRelation, exposes
get-children data + attach/detach-child actions. Retargeted events, blog,
market callers from cart to relations.

Phase 2 - Likes service (internal): unified Like model replaces ProductLike
and PostLike with generic target_type/target_slug/target_id. Exposes
is-liked, liked-slugs, liked-ids data + toggle action.

Phase 3 - PageConfig → blog: moved ownership to blog with direct DB queries,
removed proxy endpoints from cart.

Phase 4 - Orders service (public): owns Order/OrderItem + SumUp checkout
flow. Cart checkout now delegates to orders via create-order action.
Webhook/return routes and reconciliation moved to orders.

Phase 5 - Infrastructure: docker-compose, deploy.sh, Dockerfiles updated
for all 3 new services. Added orders_url helper and factory model imports.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:03:33 +00:00
6f0965aa9c Fix alembic path in entrypoints for both Docker and dev
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m56s
In Docker images, alembic.ini is at /app/alembic.ini (no service subdir).
In dev, it's at /app/{service}/alembic.ini (bind-mounted). Now tries the
service dir first, falls back to current dir.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:13:03 +00:00
e65bd41ebe Decouple per-service Alembic migrations and fix cross-DB queries
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m19s
Each service (blog, market, cart, events, federation, account) now owns
its own database schema with independent Alembic migrations. Removes the
monolithic shared/alembic/ that ran all migrations against a single DB.

- Add per-service alembic.ini, env.py, and 0001_initial.py migrations
- Add shared/db/alembic_env.py helper with table-name filtering
- Fix cross-DB FK in blog/models/snippet.py (users lives in db_account)
- Fix cart_impl.py cross-DB queries: fetch products and market_places
  via internal data endpoints instead of direct SQL joins
- Fix blog ghost_sync to fetch page_configs from cart via data endpoint
- Add products-by-ids and page-config-ensure data endpoints
- Update all entrypoint.sh to create own DB and run own migrations
- Cart now uses db_cart instead of db_market
- Add docker-compose.dev.yml, dev.sh for local development
- CI deploys both rose-ash swarm stack and rose-ash-dev compose stack
- Fix Quart namespace package crash (root_path in factory.py)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 12:07:24 +00:00
giles
507a5a66ff Remove cross-DB menu_nodes query from non-blog apps
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m19s
menu_nodes table lives in db_blog. Non-blog apps (cart, market, events,
account, federation) were querying it on their own DB session, causing
UndefinedTableError. The nav-tree fragment from blog provides the real
navigation; menu_items is now an empty fallback list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 14:18:02 +00:00
giles
961067841e Tier 0 scalability: PgBouncer, Redis split, DB split, workers
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m14s
T0.1: Separate redis-auth service (64mb, noeviction) for auth state
T0.2: Bump data Redis from 256mb to 1gb
T0.3: Per-app DATABASE_URL via PgBouncer to per-domain databases
T0.4: PgBouncer service (transaction mode, pool=20, max_conn=300);
      session.py pools reduced to 3+5 with timeout and recycle
T0.5: Hypercorn --workers 2 --keep-alive 75 on all 6 entrypoints

Deploy requires running split-databases.sh first to create per-domain
databases from the existing appdb.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 10:10:18 +00:00
giles
c6271931a6 Show per-app actor in follow notifications on Hub
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m34s
Add app_domain to APNotification model and NotificationDTO so follow
notifications display "followed you on blog" instead of just "followed
you" when the follow targets a per-app actor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:17:34 +00:00
giles
3b707ec8a0 Decouple all cross-app service calls to HTTP endpoints
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m0s
Replace every direct cross-app services.* call with HTTP-based
communication: call_action() for writes, fetch_data() for reads.
Each app now registers only its own domain service.

Infrastructure:
- shared/infrastructure/actions.py — POST client for /internal/actions/
- shared/infrastructure/data_client.py — GET client for /internal/data/
- shared/contracts/dtos.py — dto_to_dict/dto_from_dict serialization

Action endpoints (writes):
- events: 8 handlers (ticket adjust, claim/confirm, toggle, adopt)
- market: 2 handlers (create/soft-delete marketplace)
- cart: 1 handler (adopt cart for user)

Data endpoints (reads):
- blog: 4 (post-by-slug/id, posts-by-ids, search-posts)
- events: 10 (pending entries/tickets, entries/tickets for page/order,
  entry-ids, associated-entries, calendars, visible-entries-for-period)
- market: 1 (marketplaces-for-container)
- cart: 1 (cart-summary)

Service registration cleanup:
- blog→blog+federation, events→calendar+federation,
  market→market+federation, cart→cart only,
  federation→federation only, account→nothing
- Stubs reduced to minimal StubFederationService

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:01:38 +00:00
giles
580f551700 Split databases and Redis — prepare infrastructure for per-domain isolation
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m20s
Redis: per-app DB index (0-5) with shared auth DB 15 for SSO keys;
flushdb replaces flushall so deploys don't wipe cross-app auth state.

Postgres: drop 13 cross-domain FK constraints (migration v2t0p8q9r0),
remove dead ORM relationships, add explicit joins for 4 live ones.
Multi-engine sessions (account + federation) ready for per-domain DBs
via DATABASE_URL_ACCOUNT / DATABASE_URL_FEDERATION env vars.

All URLs initially point to the same appdb — zero behaviour change
until split-databases.sh is run to migrate data to per-domain DBs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 02:20:34 +00:00
giles
b3d853ad35 Add Phase 5: link-card fragments, oEmbed endpoints, OG meta
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m48s
- fetch_fragment_batch() for N+1 avoidance with per-key Redis cache
- link-card fragment handlers in blog, market, events, federation (single + batch mode)
- link_card.html templates per app with content-specific previews
- shared/infrastructure/oembed.py: build_oembed_response, build_og_meta, build_oembed_link_tag
- GET /oembed routes on blog, market, events
- og_meta + oembed_link rendering in base template <head>
- INTERNAL_URL_ARTDAG in docker-compose.yml for cross-stack fragment fetches

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:44:11 +00:00
giles
8b52a11b67 Add cart-mini + auth-menu fragments to all apps, fix like button
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m21s
- Market, events, federation, account now fetch cart-mini, auth-menu,
  and nav-tree fragments concurrently (matching blog's pattern)
- Move like button template to shared/browser/templates/ so blog can
  find it without needing market's templates in its container

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 20:26:20 +00:00
giles
e1f9c964f5 Update app READMEs for monorepo
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m56s
Remove stale submodule references, port numbers, and Running sections.
Add fragment composition details. Create READMEs for federation and account.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 20:13:00 +00:00
giles
f42042ccb7 Monorepo: consolidate 7 repos into one
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account
into a single repository. Eliminates submodule sync, sibling model
copying at build time, and per-app CI orchestration.

Changes:
- Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs
- Remove stale sibling model copies from each app
- Update all 6 Dockerfiles for monorepo build context (root = .)
- Add build directives to docker-compose.yml
- Add single .gitea/workflows/ci.yml with change detection
- Add .dockerignore for monorepo build context
- Create __init__.py for federation and account (cross-app imports)
2026-02-24 19:44:17 +00:00