Merge worktree-zero-tooling-essay into macros
Resolves conflicts by keeping both: - HEAD: cache invalidation, service worker, sw-post-message - Worktree: optimistic mutations, offline queue, action endpoint Plans.sx unified with combined 7c/7d documentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -887,6 +887,9 @@ def auto_mount_pages(app: Any, service_name: str) -> None:
|
||||
# Mount service worker at root scope for offline data layer
|
||||
mount_service_worker(app)
|
||||
|
||||
# Mount action endpoint for Phase 7c: optimistic data mutations
|
||||
mount_action_endpoint(app, service_name)
|
||||
|
||||
|
||||
def mount_pages(bp: Any, service_name: str,
|
||||
names: set[str] | list[str] | None = None) -> None:
|
||||
@@ -1225,3 +1228,56 @@ def mount_service_worker(app: Any) -> None:
|
||||
|
||||
app.add_url_rule("/sx-sw.js", endpoint="sx_service_worker", view_func=serve_sw)
|
||||
logger.info("Mounted service worker at /sx-sw.js")
|
||||
|
||||
|
||||
def mount_action_endpoint(app: Any, service_name: str) -> None:
|
||||
"""Mount /sx/action/<name> endpoint for client-side data mutations.
|
||||
|
||||
The client can POST to trigger a named action (registered via
|
||||
register_page_helpers with an 'action:' prefix). The action receives
|
||||
the JSON payload, performs the mutation, and returns the new page data
|
||||
as SX wire format.
|
||||
|
||||
This is the server counterpart to submit-mutation in orchestration.sx.
|
||||
"""
|
||||
from quart import make_response, request, abort as quart_abort
|
||||
from .parser import serialize
|
||||
from shared.browser.app.csrf import csrf_exempt
|
||||
|
||||
@csrf_exempt
|
||||
async def action_handler(name: str) -> Any:
|
||||
# Look up action helper
|
||||
helpers = get_page_helpers(service_name)
|
||||
action_fn = helpers.get(f"action:{name}")
|
||||
if action_fn is None:
|
||||
quart_abort(404)
|
||||
|
||||
# Parse JSON payload
|
||||
import asyncio as _asyncio
|
||||
data = await request.get_json(silent=True) or {}
|
||||
|
||||
try:
|
||||
result = action_fn(**data)
|
||||
if _asyncio.iscoroutine(result):
|
||||
result = await result
|
||||
except Exception as e:
|
||||
logger.warning("Action %s failed: %s", name, e)
|
||||
resp = await make_response(f'(dict "error" "{e}")', 500)
|
||||
resp.content_type = "text/sx; charset=utf-8"
|
||||
return resp
|
||||
|
||||
result_sx = serialize(result) if result is not None else "nil"
|
||||
resp = await make_response(result_sx, 200)
|
||||
resp.content_type = "text/sx; charset=utf-8"
|
||||
return resp
|
||||
|
||||
action_handler.__name__ = "sx_action"
|
||||
action_handler.__qualname__ = "sx_action"
|
||||
|
||||
app.add_url_rule(
|
||||
"/sx/action/<name>",
|
||||
endpoint="sx_action",
|
||||
view_func=action_handler,
|
||||
methods=["POST"],
|
||||
)
|
||||
logger.info("Mounted action endpoint for %s at /sx/action/<name>", service_name)
|
||||
|
||||
Reference in New Issue
Block a user