Pure SX text layout library with one IO boundary (text-measure perform). Knuth-Plass optimal line breaking, Liang's hyphenation, position calculation. Library (lib/text-layout.sx): - break-lines: Knuth-Plass DP over word widths - break-lines-greedy: simple word-wrap for comparison - hyphenate-word: Liang's trie algorithm - position-line/position-lines: running x/y sums - measure-text: single perform (text-measure IO) Server font measurement (otfm): - Reads OpenType cmap + hmtx tables from .ttf files - DejaVu Serif/Sans bundled in shared/static/fonts/ - _cek_io_resolver hook: perform works inside aser/eval_expr - JIT VM suspension inline resolution for IO in compiled code ~font component (shared/sx/templates/font.sx): - Works like ~tw: emits @font-face CSS via cssx scope - Sets font-family on parent via spread - Deduplicates font declarations Infrastructure fixes: - stdin load command: per-expression error handling (was aborting on first error) - cek_run IO hook: _cek_io_resolver in sx_types.ml - JIT VmSuspended: inline IO resolution when resolver installed - ListRef handling in IO resolver (perform creates ListRef, not List) Demo page at /sx/(applications.(pretext)): - Hero: justified paragraph with otfm-measured proportional widths - Greedy vs Knuth-Plass side-by-side comparison - Badness scoring visualization - Hyphenation syllable decomposition 25 new tests (spec/tests/test-text-layout.sx), 3201/3201 passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared
Shared infrastructure, models, contracts, services, and templates used by all five Rose Ash microservices (blog, market, cart, events, federation). Included as a git submodule in each app.
Structure
shared/
db/
base.py # SQLAlchemy declarative Base
session.py # Async session factory (get_session, register_db)
models/ # Canonical domain models
user.py # User
magic_link.py # MagicLink (auth tokens)
(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)
container_relation.py # ContainerRelation (parent-child content)
ghost_membership_entities.py # GhostNewsletter, UserNewsletter
federation.py # ActorProfile, APActivity, APFollower, APFollowing,
# RemoteActor, APRemotePost, APLocalPost,
# APInteraction, APNotification, APAnchor, IPFSPin
contracts/
dtos.py # Frozen dataclasses for cross-domain data transfer
protocols.py # Service protocols (Blog, Calendar, Market, Cart, Federation)
widgets.py # Widget types (NavWidget, CardWidget, AccountPageWidget)
services/
registry.py # Typed singleton: services.blog, .calendar, .market, .cart, .federation
blog_impl.py # SqlBlogService
calendar_impl.py # SqlCalendarService
market_impl.py # SqlMarketService
cart_impl.py # SqlCartService
federation_impl.py # SqlFederationService
federation_publish.py # try_publish() — inline AP publication helper
stubs.py # No-op stubs for absent domains
navigation.py # get_navigation_tree()
relationships.py # attach_child, get_children, detach_child
widget_registry.py # Widget registry singleton
widgets/ # Per-domain widget registration
infrastructure/
factory.py # create_base_app() — Quart app factory
cart_identity.py # current_cart_identity() (user_id or session_id)
cart_loader.py # Cart data loader for context processors
context.py # Jinja2 context processors
jinja_setup.py # Jinja2 template environment setup
urls.py # URL helpers (blog_url, market_url, etc.)
user_loader.py # Load current user from session
http_utils.py # HTTP utility functions
events/
bus.py # emit_activity(), register_activity_handler()
processor.py # EventProcessor (polls ap_activities, runs handlers)
handlers/ # Shared activity handlers
container_handlers.py # Navigation rebuild on attach/detach
login_handlers.py # Cart/entry adoption on login
order_handlers.py # Order lifecycle events
ap_delivery_handler.py # AP activity delivery to follower inboxes (wildcard)
utils/
__init__.py
calendar_helpers.py # Calendar period/entry utilities
http_signatures.py # RSA keypair generation, HTTP signature signing/verification
ipfs_client.py # Async IPFS client (add_bytes, add_json, pin_cid)
anchoring.py # Merkle trees + OpenTimestamps Bitcoin anchoring
webfinger.py # WebFinger actor resolution
browser/
app/ # Middleware, CSRF, errors, Redis caching, authz, filters
templates/ # ~300 Jinja2 templates shared across all apps
containers.py # ContainerType, container_filter, content_filter helpers
config.py # YAML config loader
log_config/setup.py # Logging configuration (JSON formatter)
static/ # Shared static assets (CSS, JS, images, FontAwesome)
editor/ # Koenig (Ghost) rich text editor build
alembic/ # Database migrations
Key Patterns
- App factory: All apps call
create_base_app()which sets up DB sessions, CSRF, error handling, event processing, logging, widget registration, and domain service wiring. - Service contracts: Cross-domain communication via typed Protocols + frozen DTO dataclasses. Apps call
services.calendar.method(), never import models from other domains. - Service registry: Typed singleton (
services.blog,.calendar,.market,.cart,.federation). Apps wire their own domain + stubs for others viaregister_domain_services(). - Activity bus:
emit_activity()writes toap_activitiestable in the caller's transaction.EventProcessorpolls pending activities and dispatches to registered handlers. Internal events usevisibility="internal"; federation activities usevisibility="public"and are delivered to follower inboxes by the wildcard delivery handler. - Widget registry: Domain services register widgets (nav, card, account); templates consume via
widgets.container_nav,widgets.container_cards. - Cart identity:
current_cart_identity()returns{"user_id": int|None, "session_id": str|None}from the request session.
Alembic Migrations
All apps share one PostgreSQL database. Migrations are managed here and run from the blog app's entrypoint (other apps skip migrations on startup).
alembic -c shared/alembic.ini upgrade head