Add standalone mode for sx-web.org deployment

- SX_STANDALONE=true env var: no OAuth, no root header, no cross-service
  fragments. Same image runs in both rose-ash cooperative and standalone.
- Factory: added no_oauth parameter to create_base_app()
- Standalone layout defcomps skip ~root-header-auto/~root-mobile-auto
- Fixed Dockerfile: was missing sx/sx/ component directory copy
- CI: deploys sx-web swarm stack on main branch when sx changes
- Stack config at ~/sx-web/ (Caddy → sx_docs, Redis)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 16:39:21 +00:00
parent 5fff83ae79
commit 0f4520d987
6 changed files with 100 additions and 8 deletions

View File

@@ -26,6 +26,7 @@ COPY sx/ ./sx-app-tmp/
RUN cp -r sx-app-tmp/app.py sx-app-tmp/path_setup.py \
sx-app-tmp/bp sx-app-tmp/sxc sx-app-tmp/services \
sx-app-tmp/content sx-app-tmp/__init__.py ./ 2>/dev/null || true && \
([ -d sx-app-tmp/sx ] && cp -r sx-app-tmp/sx ./sx || true) && \
rm -rf sx-app-tmp
# Sibling models for cross-domain SQLAlchemy imports

View File

@@ -1,10 +1,12 @@
from __future__ import annotations
import os
import path_setup # noqa: F401
from shared.infrastructure.factory import create_base_app
from bp import register_pages
from services import register_domain_services
SX_STANDALONE = os.getenv("SX_STANDALONE") == "true"
async def sx_docs_context() -> dict:
"""SX docs app context processor — fetches cross-service fragments."""
@@ -39,11 +41,29 @@ async def sx_docs_context() -> dict:
return ctx
async def sx_standalone_context() -> dict:
"""Minimal context for standalone mode — no cross-service fragments."""
from shared.infrastructure.context import base_context
ctx = await base_context()
ctx["menu_items"] = []
ctx["cart_mini"] = ""
ctx["auth_menu"] = ""
ctx["nav_tree"] = ""
return ctx
def create_app() -> "Quart":
from shared.infrastructure.factory import create_base_app
extra_kw = {}
if SX_STANDALONE:
extra_kw["no_oauth"] = True
app = create_base_app(
"sx",
context_fn=sx_docs_context,
context_fn=sx_standalone_context if SX_STANDALONE else sx_docs_context,
domain_services_fn=register_domain_services,
**extra_kw,
)
from sxc.pages import setup_sx_pages

View File

@@ -93,3 +93,45 @@
:label "sx" :href "/" :level 1 :colour "violet"
:items (~sx-main-nav :section section))
(~root-mobile-auto)))
;; ---------------------------------------------------------------------------
;; Standalone layouts (no root header, no auth — for sx-web.org)
;; ---------------------------------------------------------------------------
(defcomp ~sx-standalone-layout-full (&key section)
(~sx-header-row :nav (~sx-main-nav :section section)))
(defcomp ~sx-standalone-layout-oob (&key section)
(<> (~sx-header-row
:nav (~sx-main-nav :section section)
:oob true)
(~clear-oob-div :id "sx-header-child")))
(defcomp ~sx-standalone-layout-mobile (&key section)
(~mobile-menu-section
:label "sx" :href "/" :level 1 :colour "violet"
:items (~sx-main-nav :section section)))
(defcomp ~sx-standalone-section-layout-full (&key section sub-label sub-href sub-nav selected)
(~sx-header-row
:nav (~sx-main-nav :section section)
:child (~sx-sub-row :sub-label sub-label :sub-href sub-href
:sub-nav sub-nav :selected selected)))
(defcomp ~sx-standalone-section-layout-oob (&key section sub-label sub-href sub-nav selected)
(<> (~oob-header-sx :parent-id "sx-header-child"
:row (~sx-sub-row :sub-label sub-label :sub-href sub-href
:sub-nav sub-nav :selected selected))
(~sx-header-row
:nav (~sx-main-nav :section section)
:oob true)))
(defcomp ~sx-standalone-section-layout-mobile (&key section sub-label sub-href sub-nav)
(<>
(when sub-nav
(~mobile-menu-section
:label (or sub-label section) :href sub-href :level 2 :colour "violet"
:items sub-nav))
(~mobile-menu-section
:label "sx" :href "/" :level 1 :colour "violet"
:items (~sx-main-nav :section section))))

View File

@@ -1,11 +1,27 @@
"""SX docs layout registration — all layouts delegate to .sx defcomps."""
from __future__ import annotations
import os
def _register_sx_layouts() -> None:
"""Register the sx docs layout presets."""
from shared.sx.layouts import register_sx_layout
register_sx_layout("sx", "sx-layout-full", "sx-layout-oob", "sx-layout-mobile")
register_sx_layout("sx-section", "sx-section-layout-full",
"sx-section-layout-oob", "sx-section-layout-mobile")
if os.getenv("SX_STANDALONE") == "true":
register_sx_layout("sx",
"sx-standalone-layout-full",
"sx-standalone-layout-oob",
"sx-standalone-layout-mobile")
register_sx_layout("sx-section",
"sx-standalone-section-layout-full",
"sx-standalone-section-layout-oob",
"sx-standalone-section-layout-mobile")
else:
register_sx_layout("sx",
"sx-layout-full",
"sx-layout-oob",
"sx-layout-mobile")
register_sx_layout("sx-section",
"sx-section-layout-full",
"sx-section-layout-oob",
"sx-section-layout-mobile")