URL restructure, 404 page, trailing slash normalization, layout fixes

- Rename /reactive-islands/ → /reactive/, /reference/ → /hypermedia/reference/,
  /examples/ → /hypermedia/examples/ across all .sx and .py files
- Add 404 error page (not-found.sx) working on both server refresh and
  client-side SX navigation via orchestration.sx error response handling
- Add trailing slash redirect (GET only, excludes /api/, /static/, /internal/)
- Remove blue sky-500 header bar from SX docs layout (conditional on header-rows)
- Fix 405 on API endpoints from trailing slash redirect hitting POST/PUT/DELETE
- Fix client-side 404: orchestration.sx now swaps error response content
  instead of silently dropping it
- Add new plan files and home page component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 21:30:18 +00:00
parent e149dfe968
commit 1341c144da
35 changed files with 2305 additions and 438 deletions

View File

@@ -95,6 +95,8 @@ def create_app() -> "Quart":
),
}
app.url_map.strict_slashes = False
from sxc.pages import setup_sx_pages
setup_sx_pages()
@@ -104,6 +106,60 @@ def create_app() -> "Quart":
from shared.sx.pages import auto_mount_pages
auto_mount_pages(app, "sx")
@app.before_request
async def trailing_slash_redirect():
from quart import request, redirect
path = request.path
if (path != "/"
and not path.endswith("/")
and request.method == "GET"
and not path.startswith(("/static/", "/internal/", "/auth/"))
and "/api/" not in path
and "." not in path.rsplit("/", 1)[-1]):
qs = request.query_string.decode()
target = path + "/" + ("?" + qs if qs else "")
return redirect(target, 301)
@app.errorhandler(404)
async def sx_not_found(e):
from quart import request, make_response
from shared.browser.app.utils.htmx import is_htmx_request
from shared.sx.jinja_bridge import get_component_env, _get_request_context
from shared.sx.async_eval import async_eval_slot_to_sx
from shared.sx.types import Symbol, Keyword
from shared.sx.helpers import full_page_sx, oob_page_sx, sx_response
from shared.sx.pages import get_page_helpers
from shared.sx.page import get_template_context
path = request.path
content_ast = [
Symbol("~sx-doc"), Keyword("path"), path,
[Symbol("~not-found-content"), Keyword("path"), path],
]
env = dict(get_component_env())
env.update(get_page_helpers("sx"))
ctx = _get_request_context()
try:
content_sx = await async_eval_slot_to_sx(content_ast, env, ctx)
except Exception:
from shared.browser.app.errors import _sx_error_page
html = _sx_error_page("404", "NOT FOUND",
image="/static/errors/404.gif")
return await make_response(html, 404)
if is_htmx_request():
return sx_response(
await oob_page_sx(content=content_sx),
status=404,
)
else:
tctx = await get_template_context()
html = await full_page_sx(tctx, header_rows="",
content=content_sx)
return await make_response(html, 404)
return app