From 13bcf755f65d2bc42f67fbd159811d5542fc764a Mon Sep 17 00:00:00 2001 From: giles Date: Mon, 2 Mar 2026 23:22:01 +0000 Subject: [PATCH] Add OOB header swaps for sx docs navigation + enable OAuth + fragments - OOB nav updates: AJAX navigation now swaps both menu bar levels (main nav highlighting + sub-nav with current page) using the same oob_header_sx/oob_page_sx pattern as blog/market/events - Enable OAuth for sx and test apps (removed from _NO_OAUTH, added sx to ALLOWED_CLIENTS, added app_urls for sx/test/orders) - Fetch real cross-service fragments (cart-mini, auth-menu, nav-tree) instead of hardcoding empty values - Add :selected param to ~menu-row-sx for white text current-page label - Fix duplicate element IDs: use menu-row-sx child_id/child mechanism instead of manual header_child_sx wrappers - Fix home page copy: "Server-rendered DOM over the wire (no HTML)" Co-Authored-By: Claude Opus 4.6 --- _config/app-config.yaml | 3 + account/bp/auth/routes.py | 2 +- shared/infrastructure/factory.py | 4 +- shared/sx/templates/layout.sx | 9 +- sx/app.py | 37 +- sx/bp/pages/routes.py | 28 +- sx/sxc/home.sx | 8 +- sx/sxc/sx_components.py | 686 ++++++++++++++++++------------- 8 files changed, 461 insertions(+), 316 deletions(-) diff --git a/_config/app-config.yaml b/_config/app-config.yaml index dabb4c3..48020ab 100644 --- a/_config/app-config.yaml +++ b/_config/app-config.yaml @@ -16,6 +16,9 @@ app_urls: events: "https://events.rose-ash.com" federation: "https://federation.rose-ash.com" account: "https://account.rose-ash.com" + sx: "https://sx.rose-ash.com" + test: "https://test.rose-ash.com" + orders: "https://orders.rose-ash.com" cache: fs_root: /app/_snapshot # <- absolute path to your snapshot dir categories: diff --git a/account/bp/auth/routes.py b/account/bp/auth/routes.py index a49d96a..4727051 100644 --- a/account/bp/auth/routes.py +++ b/account/bp/auth/routes.py @@ -44,7 +44,7 @@ from .services import ( SESSION_USER_KEY = "uid" ACCOUNT_SESSION_KEY = "account_sid" -ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation", "orders", "test", "artdag", "artdag_l2"} +ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation", "orders", "test", "sx", "artdag", "artdag_l2"} def register(url_prefix="/auth"): diff --git a/shared/infrastructure/factory.py b/shared/infrastructure/factory.py index dbf821b..4799943 100644 --- a/shared/infrastructure/factory.py +++ b/shared/infrastructure/factory.py @@ -145,8 +145,8 @@ def create_base_app( errors(app) # Auto-register OAuth client blueprint for non-account apps - # (account is the OAuth authorization server; test/sx are public dashboards) - _NO_OAUTH = {"account", "test", "sx"} + # (account is the OAuth authorization server) + _NO_OAUTH = {"account"} if name not in _NO_OAUTH: from shared.infrastructure.oauth import create_oauth_blueprint app.register_blueprint(create_oauth_blueprint(name)) diff --git a/shared/sx/templates/layout.sx b/shared/sx/templates/layout.sx index eb991f2..02e1fd4 100644 --- a/shared/sx/templates/layout.sx +++ b/shared/sx/templates/layout.sx @@ -82,9 +82,9 @@ (div :class "block md:hidden text-md font-bold" (when auth-menu auth-menu)))) -; @css bg-sky-400 bg-sky-300 bg-sky-200 bg-sky-100 +; @css bg-sky-400 bg-sky-300 bg-sky-200 bg-sky-100 bg-violet-400 bg-violet-300 bg-violet-200 bg-violet-100 (defcomp ~menu-row-sx (&key id level colour link-href link-label link-label-content icon - hx-select nav child-id child oob external) + selected hx-select nav child-id child oob external) (let* ((c (or colour "sky")) (lv (or level 1)) (shade (str (- 500 (* lv 100))))) @@ -102,7 +102,10 @@ :class "w-full whitespace-normal flex items-center gap-2 font-bold text-2xl px-3 py-2" (when icon (i :class icon :aria-hidden "true")) (if link-label-content link-label-content - (when link-label (div link-label))))) + (<> + (when link-label (div link-label)) + (when selected + (span :class "text-lg text-white/80 font-normal" selected)))))) (when nav (nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0" nav))) diff --git a/sx/app.py b/sx/app.py index 5627281..ae9d306 100644 --- a/sx/app.py +++ b/sx/app.py @@ -3,28 +3,41 @@ import path_setup # noqa: F401 import sxc.sx_components as sx_components # noqa: F401 from shared.infrastructure.factory import create_base_app -from shared.sx.jinja_bridge import render from bp import register_pages from services import register_domain_services async def sx_docs_context() -> dict: - """SX docs app context processor — minimal, no cross-service fragments.""" + """SX docs app context processor — fetches cross-service fragments.""" + from quart import request, g from shared.infrastructure.context import base_context + from shared.infrastructure.cart_identity import current_cart_identity + from shared.infrastructure.fragments import fetch_fragments ctx = await base_context() ctx["menu_items"] = [] - blog_url = ctx.get("blog_url", "") - if callable(blog_url): - blog_url_str = blog_url("") - else: - blog_url_str = str(blog_url or "") - ctx["cart_mini"] = render( - "cart-mini", cart_count=0, blog_url=blog_url_str, cart_url="", - ) - ctx["auth_menu"] = "" - ctx["nav_tree"] = "" + + ident = current_cart_identity() + user = getattr(g, "user", None) + + cart_params = {} + if ident.get("user_id"): + cart_params["user_id"] = ident["user_id"] + if ident.get("session_id"): + cart_params["session_id"] = ident["session_id"] + + auth_params = {"email": user.email} if user else None + + cart_mini, auth_menu, nav_tree = await fetch_fragments([ + ("cart", "cart-mini", cart_params or None), + ("account", "auth-menu", auth_params), + ("blog", "nav-tree", {"app_name": "sx", "path": request.path}), + ], required=False) + ctx["cart_mini"] = cart_mini + ctx["auth_menu"] = auth_menu + ctx["nav_tree"] = nav_tree + return ctx diff --git a/sx/bp/pages/routes.py b/sx/bp/pages/routes.py index ebbaab1..e1cda0c 100644 --- a/sx/bp/pages/routes.py +++ b/sx/bp/pages/routes.py @@ -20,8 +20,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def index(): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import home_content_sx - return sx_response(home_content_sx()) + from sxc.sx_components import home_oob_sx + return sx_response(await home_oob_sx()) from shared.sx.page import get_template_context from sxc.sx_components import render_home_page_sx @@ -42,8 +42,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def docs_page(slug: str): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import docs_content_partial_sx - return sx_response(docs_content_partial_sx(slug)) + from sxc.sx_components import docs_oob_sx + return sx_response(await docs_oob_sx(slug)) from shared.sx.page import get_template_context from sxc.sx_components import render_docs_page_sx @@ -59,8 +59,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def reference_index(): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import reference_content_partial_sx - return sx_response(reference_content_partial_sx("")) + from sxc.sx_components import reference_oob_sx + return sx_response(await reference_oob_sx("")) from shared.sx.page import get_template_context from sxc.sx_components import render_reference_page_sx @@ -72,8 +72,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def reference_page(slug: str): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import reference_content_partial_sx - return sx_response(reference_content_partial_sx(slug)) + from sxc.sx_components import reference_oob_sx + return sx_response(await reference_oob_sx(slug)) from shared.sx.page import get_template_context from sxc.sx_components import render_reference_page_sx @@ -94,8 +94,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def protocol_page(slug: str): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import protocol_content_partial_sx - return sx_response(protocol_content_partial_sx(slug)) + from sxc.sx_components import protocol_oob_sx + return sx_response(await protocol_oob_sx(slug)) from shared.sx.page import get_template_context from sxc.sx_components import render_protocol_page_sx @@ -116,8 +116,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def examples_page(slug: str): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import examples_content_partial_sx - return sx_response(examples_content_partial_sx(slug)) + from sxc.sx_components import examples_oob_sx + return sx_response(await examples_oob_sx(slug)) from shared.sx.page import get_template_context from sxc.sx_components import render_examples_page_sx @@ -205,8 +205,8 @@ def register(url_prefix: str = "/") -> Blueprint: async def essay_page(slug: str): if _is_sx_request(): from shared.sx.helpers import sx_response - from sxc.sx_components import essay_content_partial_sx - return sx_response(essay_content_partial_sx(slug)) + from sxc.sx_components import essay_oob_sx + return sx_response(await essay_oob_sx(slug)) from shared.sx.page import get_template_context from sxc.sx_components import render_essay_page_sx diff --git a/sx/sxc/home.sx b/sx/sxc/home.sx index 68998a9..06ee154 100644 --- a/sx/sxc/home.sx +++ b/sx/sxc/home.sx @@ -1,6 +1,6 @@ ;; SX docs — home page components -(defcomp ~sx-hero () +(defcomp ~sx-hero (&key &rest children) (div :class "max-w-4xl mx-auto px-6 py-16 text-center" (h1 :class "text-5xl font-bold text-stone-900 mb-4" (span :class "text-violet-600" "sx")) @@ -10,9 +10,7 @@ "A hypermedia-driven UI engine that combines htmx's server-first philosophy " "with React's component model. All rendered via s-expressions over the wire.") (div :class "bg-stone-50 border border-stone-200 rounded-lg p-6 text-left font-mono text-sm overflow-x-auto" - (pre - (code :class "language-lisp" - "(defcomp ~greeting (&key name)\n (div :class \"p-4 rounded bg-violet-50\"\n (h2 :class \"text-lg font-bold\"\n (str \"Hello, \" name \"!\"))\n (button\n :sx-get \"/api/greet\"\n :sx-target \"closest div\"\n :sx-swap \"outerHTML\"\n :class \"mt-2 px-4 py-2 bg-violet-600 text-white rounded\"\n \"Refresh\")))"))))) + (pre :class "leading-relaxed" children)))) (defcomp ~sx-philosophy () (div :class "max-w-4xl mx-auto px-6 py-12" @@ -21,7 +19,7 @@ (div :class "space-y-4" (h3 :class "text-xl font-semibold text-violet-700" "From htmx") (ul :class "space-y-2 text-stone-600" - (li "Server-rendered HTML over the wire") + (li "Server-rendered DOM over the wire (no HTML)") (li "Hypermedia attributes on any element (sx-get, sx-post, ...)") (li "Target/swap model for partial page updates") (li "No client-side routing, no virtual DOM") diff --git a/sx/sxc/sx_components.py b/sx/sxc/sx_components.py index 48ee0fe..ac2e672 100644 --- a/sx/sxc/sx_components.py +++ b/sx/sxc/sx_components.py @@ -5,9 +5,11 @@ import os from shared.sx.jinja_bridge import load_sx_dir, watch_sx_dir from shared.sx.helpers import ( - sx_call, SxExpr, - root_header_sx, full_page_sx, header_child_sx, + sx_call, SxExpr, get_asset_url, + root_header_sx, full_page_sx, + oob_header_sx, oob_page_sx, ) +from content.highlight import highlight # Load .sx components from sxc/ directory (not sx/ to avoid name collision) _sxc_dir = os.path.dirname(__file__) @@ -15,6 +17,23 @@ load_sx_dir(_sxc_dir) watch_sx_dir(_sxc_dir) +def _full_page(ctx: dict, **kwargs) -> str: + """full_page_sx wrapper.""" + return full_page_sx(ctx, **kwargs) + + +def _code(code: str, language: str = "lisp") -> str: + """Build a ~doc-code component with highlighted content.""" + highlighted = highlight(code, language) + return f'(~doc-code {highlighted})' + + +def _example_code(code: str) -> str: + """Build an ~example-source component with highlighted content.""" + highlighted = highlight(code, "lisp") + return f'(~example-source {highlighted})' + + # --------------------------------------------------------------------------- # Navigation helpers # --------------------------------------------------------------------------- @@ -31,13 +50,14 @@ def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> s return "(<> " + " ".join(parts) + ")" -def _sx_header_sx(nav: str | None = None) -> str: +def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str: """Build the sx docs menu-row.""" return sx_call("menu-row-sx", id="sx-row", level=1, colour="violet", link_href="/", link_label="sx", icon="fa fa-code", nav=SxExpr(nav) if nav else None, child_id="sx-header-child", + child=SxExpr(child) if child else None, ) @@ -74,24 +94,118 @@ def _main_nav_sx(current_section: str | None = None) -> str: def _header_stack_sx(ctx: dict, section_nav: str | None = None) -> str: """Full header stack: root header + sx menu row.""" hdr = root_header_sx(ctx) - inner = _sx_header_sx(section_nav) - child = header_child_sx(inner) - return "(<> " + hdr + " " + child + ")" + sx_row = _sx_header_sx(section_nav) + return "(<> " + hdr + " " + sx_row + ")" + + +def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str, + selected: str = "") -> str: + """Build the level-2 sub-section menu-row.""" + return sx_call("menu-row-sx", + id="sx-sub-row", level=2, colour="violet", + link_href=sub_href, link_label=sub_label, + selected=selected or None, + nav=SxExpr(sub_nav), + ) def _section_header_stack_sx(ctx: dict, main_nav: str, sub_nav: str, - sub_label: str, sub_href: str) -> str: + sub_label: str, sub_href: str, + selected: str = "") -> str: """Header stack with main nav + sub-section nav row.""" hdr = root_header_sx(ctx) + sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected) + sx_row = _sx_header_sx(main_nav, child=sub_row) + return "(<> " + hdr + " " + sx_row + ")" + + +# --------------------------------------------------------------------------- +# OOB helpers — rebuild header rows for AJAX navigation +# --------------------------------------------------------------------------- + +async def _section_oob_sx(section: str, sub_label: str, sub_href: str, + sub_nav: str, content: str, + selected: str = "") -> str: + """Generic OOB response: rebuild both header rows + content.""" + from shared.sx.page import get_template_context + ctx = await get_template_context() + root_hdr = root_header_sx(ctx) + main_nav = _main_nav_sx(section) + sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected) + sx_row = _sx_header_sx(main_nav, child=sub_row) + rows = "(<> " + root_hdr + " " + sx_row + ")" + header_oob = oob_header_sx("root-header-child", "sx-header-child", rows) + return oob_page_sx(oobs=header_oob, content=content) + + +async def home_oob_sx() -> str: + """OOB response for home page navigation.""" + from shared.sx.page import get_template_context + ctx = await get_template_context() + root_hdr = root_header_sx(ctx) + main_nav = _main_nav_sx() sx_row = _sx_header_sx(main_nav) - sub_row = sx_call("menu-row-sx", - id="sx-sub-row", level=2, colour="violet", - link_href=sub_href, link_label=sub_label, - nav=SxExpr(sub_nav), + rows = "(<> " + root_hdr + " " + sx_row + ")" + header_oob = oob_header_sx("root-header-child", "sx-header-child", rows) + hero_code = highlight('(div :class "p-4 bg-white rounded shadow"\n' + ' (h1 :class "text-2xl font-bold" "Hello")\n' + ' (button :sx-get "/api/data"\n' + ' :sx-target "#result"\n' + ' "Load data"))', "lisp") + content = ( + f'(div :id "main-content"' + f' (~sx-hero {hero_code})' + f' (~sx-philosophy)' + f' (~sx-how-it-works)' + f' (~sx-credits))' ) - inner = "(<> " + sx_row + " " + header_child_sx(sub_row, id="sx-header-child") + ")" - child = header_child_sx(inner) - return "(<> " + hdr + " " + child + ")" + return oob_page_sx(oobs=header_oob, content=content) + + +async def docs_oob_sx(slug: str) -> str: + """OOB response for docs section navigation.""" + from content.pages import DOCS_NAV + current = next((label for label, href in DOCS_NAV if href.endswith(slug)), None) + sub_nav = _docs_nav_sx(current) + return await _section_oob_sx("Docs", "Docs", "/docs/introduction", sub_nav, + _docs_content_sx(slug), selected=current or "") + + +async def reference_oob_sx(slug: str) -> str: + """OOB response for reference section navigation.""" + from content.pages import REFERENCE_NAV + current = next((label for label, href in REFERENCE_NAV + if href.rstrip("/").endswith(slug or "reference")), "Attributes") + sub_nav = _reference_nav_sx(current) + return await _section_oob_sx("Reference", "Reference", "/reference/", sub_nav, + _reference_content_sx(slug), selected=current or "") + + +async def protocol_oob_sx(slug: str) -> str: + """OOB response for protocols section navigation.""" + from content.pages import PROTOCOLS_NAV + current = next((label for label, href in PROTOCOLS_NAV if href.endswith(slug)), None) + sub_nav = _protocols_nav_sx(current) + return await _section_oob_sx("Protocols", "Protocols", "/protocols/wire-format", sub_nav, + _protocol_content_sx(slug), selected=current or "") + + +async def examples_oob_sx(slug: str) -> str: + """OOB response for examples section navigation.""" + from content.pages import EXAMPLES_NAV + current = next((label for label, href in EXAMPLES_NAV if href.endswith(slug)), None) + sub_nav = _examples_nav_sx(current) + return await _section_oob_sx("Examples", "Examples", "/examples/click-to-load", sub_nav, + _examples_content_sx(slug), selected=current or "") + + +async def essay_oob_sx(slug: str) -> str: + """OOB response for essays section navigation.""" + from content.pages import ESSAYS_NAV + current = next((label for label, href in ESSAYS_NAV if href.endswith(slug)), None) + sub_nav = _essays_nav_sx(current) + return await _section_oob_sx("Essays", "Essays", "/essays/sx-sucks", sub_nav, + _essay_content_sx(slug), selected=current or "") # --------------------------------------------------------------------------- @@ -169,14 +283,19 @@ async def render_home_page_sx(ctx: dict) -> str: """Full page: home.""" main_nav = _main_nav_sx() hdr = _header_stack_sx(ctx, main_nav) + hero_code = highlight('(div :class "p-4 bg-white rounded shadow"\n' + ' (h1 :class "text-2xl font-bold" "Hello")\n' + ' (button :sx-get "/api/data"\n' + ' :sx-target "#result"\n' + ' "Load data"))', "lisp") content = ( - '(div :id "main-content"' - ' (~sx-hero)' - ' (~sx-philosophy)' - ' (~sx-how-it-works)' - ' (~sx-credits))' + f'(div :id "main-content"' + f' (~sx-hero {hero_code})' + f' (~sx-philosophy)' + f' (~sx-how-it-works)' + f' (~sx-credits))' ) - return full_page_sx(ctx, header_rows=hdr, content=content) + return _full_page(ctx, header_rows=hdr, content=content) async def render_docs_page_sx(ctx: dict, slug: str) -> str: @@ -185,9 +304,10 @@ async def render_docs_page_sx(ctx: dict, slug: str) -> str: current = next((label for label, href in DOCS_NAV if href.endswith(slug)), None) main_nav = _main_nav_sx("Docs") sub_nav = _docs_nav_sx(current) - hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Docs", "/docs/introduction") + hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Docs", "/docs/introduction", + selected=current or "") content = _docs_content_sx(slug) - return full_page_sx(ctx, header_rows=hdr, content=content) + return _full_page(ctx, header_rows=hdr, content=content) def _docs_content_sx(slug: str) -> str: @@ -235,98 +355,92 @@ def _docs_introduction_sx() -> str: def _docs_getting_started_sx() -> str: + c1 = _code('(div :class "p-4 bg-white rounded"\n (h1 :class "text-2xl font-bold" "Hello, world!")\n (p "This is rendered from an s-expression."))') + c2 = _code('(button\n :sx-get "/api/data"\n :sx-target "#result"\n :sx-swap "innerHTML"\n "Load data")') return ( - '(~doc-page :title "Getting Started"' - ' (~doc-section :title "Minimal example" :id "minimal"' - ' (p :class "text-stone-600"' - ' "An sx response is s-expression source code with content type text/sx:")' - ' (~doc-code :language "lisp" :code' - ' "(div :class \\"p-4 bg-white rounded\\"\\n' - ' (h1 :class \\"text-2xl font-bold\\" \\"Hello, world!\\")\\n' - ' (p \\"This is rendered from an s-expression.\\"))")' - ' (p :class "text-stone-600"' - ' "Add sx-get to any element to make it fetch and render sx:"))' - ' (~doc-section :title "Hypermedia attributes" :id "attrs"' - ' (p :class "text-stone-600"' - ' "Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")' - ' (~doc-code :language "lisp" :code' - ' "(button\\n' - ' :sx-get \\"/api/data\\"\\n' - ' :sx-target \\"#result\\"\\n' - ' :sx-swap \\"innerHTML\\"\\n' - ' \\"Load data\\")")' - ' (p :class "text-stone-600"' - ' "sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. ' - 'The response is parsed as sx and rendered into the target element.")))' + f'(~doc-page :title "Getting Started"' + f' (~doc-section :title "Minimal example" :id "minimal"' + f' (p :class "text-stone-600"' + f' "An sx response is s-expression source code with content type text/sx:")' + f' {c1}' + f' (p :class "text-stone-600"' + f' "Add sx-get to any element to make it fetch and render sx:"))' + f' (~doc-section :title "Hypermedia attributes" :id "attrs"' + f' (p :class "text-stone-600"' + f' "Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")' + f' {c2}' + f' (p :class "text-stone-600"' + f' "sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. ' + f'The response is parsed as sx and rendered into the target element.")))' ) def _docs_components_sx() -> str: + c1 = _code('(defcomp ~card (&key title subtitle &rest children)\n' + ' (div :class "border rounded p-4"\n' + ' (h2 :class "font-bold" title)\n' + ' (when subtitle (p :class "text-stone-500" subtitle))\n' + ' (div :class "mt-3" children)))') + c2 = _code('(~card :title "My Card" :subtitle "A description"\n' + ' (p "First child")\n' + ' (p "Second child"))') return ( - '(~doc-page :title "Components"' - ' (~doc-section :title "defcomp" :id "defcomp"' - ' (p :class "text-stone-600"' - ' "Components are defined with defcomp. They take keyword parameters and optional children:")' - ' (~doc-code :language "lisp" :code' - ' "(defcomp ~card (&key title subtitle &rest children)\\n' - ' (div :class \\"border rounded p-4\\"\\n' - ' (h2 :class \\"font-bold\\" title)\\n' - ' (when subtitle (p :class \\"text-stone-500\\" subtitle))\\n' - ' (div :class \\"mt-3\\" children)))")' - ' (p :class "text-stone-600"' - ' "Use components with the ~ prefix:") ' - ' (~doc-code :language "lisp" :code' - ' "(~card :title \\"My Card\\" :subtitle \\"A description\\"\\n' - ' (p \\"First child\\")\\n' - ' (p \\"Second child\\"))"))' - ' (~doc-section :title "Component caching" :id "caching"' - ' (p :class "text-stone-600"' - ' "Component definitions are sent in a