Files
rose-ash/docs/jinja-to-sx-migration.md
giles e6b0849ce3
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m19s
Add Jinja-to-sx migration plan
Documents remaining 24 render_template() calls across events, blog,
and orders services with phased conversion strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 02:16:47 +00:00

9.2 KiB

Replace Remaining Jinja Templates with S-Expressions

Context

The Rose Ash codebase is migrating from Jinja2 HTML templates to the sx s-expression component system. Most routes are already converted. 24 render_template() calls remain across 3 services (events, blog, orders). Email templates (magic_link) stay as Jinja — they render server-side for email delivery, not browser rendering.


Phase 1: Events Route Wiring (16 calls → 0)

The sx render functions already exist in events/sx/sx_components.py. The routes just haven't been updated to call them.

1a. events/bp/slot/routes.py — 3 calls

  • get(): Replace render_template("_types/slot/index.html") / _oob_elements.html with render_slot_page(tctx) / render_slot_oob(tctx) using standard HTMX branch pattern
  • get_edit(): Replace render_template("_types/slot/_edit.html") with render_slot_edit_form(slot, g.calendar)sx_response()

1b. events/bp/slots/routes.py — 2 calls

  • add_form(): Replace with render_slot_add_form(g.calendar)sx_response()
  • add_button(): Replace with render_slot_add_button(g.calendar)sx_response()

1c. events/bp/ticket_types/routes.py — 4 calls

  • get(): Replace full page / OOB with render_ticket_types_page(tctx) / render_ticket_types_oob(tctx)
  • add_form(): Replace with sx render function → sx_response()
  • add_button(): Replace with sx render function → sx_response()

1d. events/bp/ticket_type/routes.py — 3 calls

  • get(): Replace full page / OOB with render_ticket_type_page(tctx) / render_ticket_type_oob(tctx)
  • get_edit(): Replace with render_ticket_type_edit_form(...)sx_response()

1e. events/bp/calendar_entries/routes.py — 2 calls

  • add_form(): Replace with render_entry_add_form(g.calendar, day, month, year, day_slots)sx_response()
  • add_button(): Replace with render_entry_add_button(g.calendar, day, month, year)sx_response() (need to check if this exists, create if not)

1f. events/bp/calendar_entry/routes.py — 2 calls

  • get_edit(): Replace with render_entry_edit_form(entry, g.calendar, day, month, year, day_slots)sx_response()
  • search_posts(): Replace with render_post_search_results(search_posts, query, page, total_pages, ...)sx_response()

1g. events/bp/calendar_entry/admin/routes.py — 2 calls

  • admin(): Replace with render_entry_admin_page(tctx) / render_entry_admin_oob(tctx) using standard HTMX branch pattern

Verification

  • ./dev.sh events — starts without errors
  • Navigate to each admin page: slot detail/edit, slots add, ticket types list/add, ticket type detail/edit, calendar entry add/edit, entry admin
  • Verify HTMX interactions: form add/cancel/save, OOB header swaps

Phase 2: Orders Checkout Return (2 calls → 0)

2a. Create orders/sx/checkout.sx

Define ~checkout-return-content component covering: status heading, order summary (reuse existing ~order-summary-card etc.), line items, calendar entries, ticket items, status-dependent messaging.

2b. Add render function in orders/sx/sx_components.py

  • async def render_checkout_return_page(ctx, order, status, calendar_entries, order_tickets) — builds header rows + content, wraps in full_page_sx()
  • No OOB variant needed (direct-navigation-only page)

2c. Update orders/bp/checkout/routes.py

Replace both render_template("_types/cart/checkout_return.html", ...) calls with render_checkout_return_page().

Verification

  • Test checkout flow: paid, failed, missing order states
  • Verify order items and calendar entries display correctly

Phase 3: Blog Menu Items (3 calls → 0)

3a. Create blog/sx/menu_items.sx

  • ~menu-item-form — add/edit form with page search input, selected page display, hidden inputs
  • ~page-search-results — search result list with infinite scroll sentinel
  • ~page-search-item — individual page result row

3b. Add render functions in blog/sx/sx_components.py

  • def render_menu_item_form(menu_item=None) -> str
  • def render_page_search_results(pages, query, page, has_more) -> str

3c. Update blog/bp/menu_items/routes.py

Replace all 3 render_template() calls with sx render functions → sx_response().

Verification

  • Settings > Menu Items > Add/Edit — form renders, page search works, save succeeds
  • Search with pagination (infinite scroll) works

Phase 4: Blog Post Admin Panels (5 calls → 0)

These currently use a hybrid pattern: Jinja renders HTML, route passes it as data_html/entries_html/etc., and _raw_html_sx() wraps it in (raw! "..."). Converting means building content natively in sx instead.

4a. Post Data panel — _types/post_data/_main_panel.html

Approach: Build _post_data_content_sx(ctx) in sx_components.py using Python code to walk ORM model columns/relationships (cleaner than Jinja for metadata introspection). Add data inspector components to blog/sx/admin.sx.

Modify: blog/bp/post/admin/routes.py data() — remove render_template(), pass context directly. blog/sx/sx_components.py — replace _raw_html_sx(ctx.get("data_html")) with native _post_data_content_sx(ctx).

4b. Post Entries panel — _types/post_entries/_main_panel.html

Approach: Build _post_entries_content_sx(ctx) showing associated entries + calendar browsers with lazy-loaded calendar views.

Modify: Same pattern — remove render_template() from entries() route, build content in sx.

4c. Calendar View — _types/post/admin/_calendar_view.html

Approach: Build render_calendar_view_sx(calendar, year, month, ...) producing month grid with entry toggle buttons. The HTMX attributes (sx-post, sx-trigger) translate directly to sx keyword args.

Modify: blog/bp/post/admin/routes.py calendar_view() — return sx_response() instead of render_template().

4d. Post Settings panel — _types/post_settings/_main_panel.html

Approach: Build settings form with collapsible <details> sections (General, Tags, Feature Image, SEO, Social, Advanced). Jinja macros (field_label, text_input, etc.) become reusable (defcomp ...) components. Add to blog/sx/settings.sx.

Modify: Same pattern as 4a — replace _raw_html_sx(ctx.get("settings_html")).

4e. Post Edit panel — _types/post_edit/_main_panel.html (352 lines, WYSIWYG)

Approach: Feature image upload/preview, title/slug editing, Lexical editor container, newsletter checkboxes, save footer. The tightly-coupled JavaScript (Lexical init, image upload, slug generation) stays as (script ...) or (raw! "<script>...") blocks. Structural HTML converts to sx components in blog/sx/editor.sx.

Modify: Same pattern — replace _raw_html_sx(ctx.get("edit_html")).

Verification

  • Post admin tabs: data, entries, settings, edit — all render correctly
  • Calendar view lazy-loads inside entries panel
  • Settings form saves and preserves values
  • WYSIWYG editor: feature image add/remove, Lexical loads, newsletters, save/publish

Implementation Order

# Scope Calls Effort New files
1 Events route wiring 16 Low — render functions exist None
2 Orders checkout 2 Medium — new components orders/sx/checkout.sx
3 Blog menu items 3 Medium — new components + JS blog/sx/menu_items.sx
4a Blog post data 1 Medium — ORM introspection
4b Blog post entries 1 Medium
4c Blog calendar view 1 Medium
4d Blog post settings 1 Medium-high — large form
4e Blog post edit 1 High — WYSIWYG editor

Files Modified

File Change
events/bp/slot/routes.py Wire to existing sx render functions
events/bp/slots/routes.py Wire to existing sx render functions
events/bp/ticket_types/routes.py Wire to existing sx render functions
events/bp/ticket_type/routes.py Wire to existing sx render functions
events/bp/calendar_entries/routes.py Wire to existing sx render functions
events/bp/calendar_entry/routes.py Wire to existing sx render functions
events/bp/calendar_entry/admin/routes.py Wire to existing sx render functions
orders/sx/checkout.sx NEW — checkout return components
orders/sx/sx_components.py Add render_checkout_return_page()
orders/bp/checkout/routes.py Use sx render function
blog/sx/menu_items.sx NEW — menu item form components
blog/sx/sx_components.py Add menu item + post admin render functions
blog/sx/admin.sx Add data/entries/calendar components
blog/sx/settings.sx Add settings form components
blog/sx/editor.sx Add WYSIWYG editor components
blog/bp/menu_items/routes.py Use sx render functions
blog/bp/post/admin/routes.py Remove all 5 render_template() calls

Final Verification

  1. python3 -m pytest shared/sx/tests/ -v — all sx tests pass
  2. ./dev.sh events — events starts, all admin pages work
  3. ./dev.sh blog — blog starts, all post admin tabs work
  4. ./dev.sh orders — orders starts, checkout return renders
  5. Zero render_template() calls remaining (except email templates)