From b4a9bb8bae02b896aed57695546f73d9df88d9c4 Mon Sep 17 00:00:00 2001 From: Jer Miller Date: Mon, 6 Jul 2026 19:55:40 -0600 Subject: [PATCH] feat(convey): tear down the Jinja shell; guard against its return MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SPA conversion (L1–L6) flipped all 22 owner apps to the static shell (convey/static/shell.html). This capstone removes the now-dead Jinja container and makes shell-serving unconditional: - Delete the unrendered Jinja shell: templates/{app.html, menu_bar, date_nav, status_pane, diagnostic_console, chat_bar}.html. Only the static, construct-free init.html remains under convey/templates/. - Retire the transitional `spa` app flag end-to-end: drop the App dataclass field, its parse, all 22 app.json entries, and the /api/shell payload key. The injected index route now always serves shell.html and the workspace fragment route serves unconditionally; workspace_url is non-null for every app. - Remove the orphaned App.get_workspace_template (get_background_template is retained and unchanged). - Add an AST-based CI guard to scripts/check_api_conventions.py: flag any flask render_template(...) call under solstone/apps + solstone/convey (excluding tests), resolved by import binding so the local provider_readiness._render_template helper is not matched. The two PDF routes (news, reflections) are the only allowlisted call sites. A construct-free guard asserts convey/templates/ carries no Jinja markers. Executable negative controls prove both guards fire. No app's rendered output changes — this is dead-code removal plus an anti-regression guard. Docs (CONVEY-FRONTEND.md, APPS.md, CONVEY.md) updated to retire the flag and note the guard. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/APPS.md | 23 ++- docs/CONVEY-FRONTEND.md | 21 +-- docs/CONVEY.md | 5 +- scripts/check_api_conventions.py | 105 ++++++++++- solstone/apps/__init__.py | 30 +--- solstone/apps/activities/app.json | 1 - solstone/apps/backup/app.json | 3 +- solstone/apps/backup/tests/test_routes.py | 5 - solstone/apps/chat/app.json | 3 +- solstone/apps/curation/app.json | 1 - solstone/apps/curation/tests/test_routes.py | 1 - solstone/apps/entities/app.json | 3 +- solstone/apps/health/app.json | 3 +- .../health/tests/test_workspace_template.py | 2 - solstone/apps/home/app.json | 3 +- solstone/apps/home/tests/test_spa.py | 3 - solstone/apps/import/app.json | 3 +- solstone/apps/network/app.json | 1 - solstone/apps/news/app.json | 1 - solstone/apps/observer/app.json | 1 - solstone/apps/reflections/app.json | 1 - solstone/apps/search/app.json | 3 +- solstone/apps/settings/app.json | 3 +- solstone/apps/sol/app.json | 1 - solstone/apps/speakers/app.json | 1 - solstone/apps/stats/app.json | 1 - .../stats/tests/test_workspace_template.py | 6 - solstone/apps/support/app.json | 1 - solstone/apps/thinking/app.json | 1 - solstone/apps/timeline/app.json | 1 - solstone/apps/tokens/app.json | 1 - solstone/apps/transcripts/app.json | 1 - solstone/convey/shell_data.py | 5 +- solstone/convey/templates/app.html | 170 ------------------ solstone/convey/templates/chat_bar.html | 61 ------- solstone/convey/templates/date_nav.html | 127 ------------- .../convey/templates/diagnostic_console.html | 24 --- solstone/convey/templates/menu_bar.html | 26 --- solstone/convey/templates/status_pane.html | 43 ----- solstone/convey/tests/test_static_cache.py | 12 -- tests/test_app_news.py | 1 - tests/test_app_reflections.py | 1 - tests/test_check_api_conventions.py | 47 ++++- tests/test_convey_apps.py | 60 +------ tests/test_convey_spa_shell.py | 20 ++- 45 files changed, 191 insertions(+), 645 deletions(-) delete mode 100644 solstone/convey/templates/app.html delete mode 100644 solstone/convey/templates/chat_bar.html delete mode 100644 solstone/convey/templates/date_nav.html delete mode 100644 solstone/convey/templates/diagnostic_console.html delete mode 100644 solstone/convey/templates/menu_bar.html delete mode 100644 solstone/convey/templates/status_pane.html diff --git a/docs/APPS.md b/docs/APPS.md index c698d316b..c3b1f7005 100644 --- a/docs/APPS.md +++ b/docs/APPS.md @@ -88,7 +88,8 @@ See `solstone/apps/__init__.py` for discovery logic and route injection. ### 1. `workspace.html` - Main Content -The workspace template is included inside the app container (`app.html`). +The workspace fragment is served verbatim and mounted into the static shell +(`convey/static/shell.html`) at `/app/{name}/`. **Available Template Context:** - `app` - Current app name (auto-injected from URL) @@ -148,14 +149,12 @@ Override default icon, label, and other app settings. - `date_nav` - Show date navigation bar (default: false) - `app_bar` - Show the universal chat bar (default: true) - `allow_future_dates` - Allow clicking future dates in month picker (default: false) -- `spa` - Transitional opt-in for the static shell and `/workspace` fragment route (default: false) **When to disable facets:** Set `"facets": false` for apps that don't use facet-based organization (e.g., system settings, dev tools). -**Static shell default:** New apps should use `"spa": true` with a -construct-free `workspace.html`: no `{{`, `{%`, or `{#` Jinja constructs. -SPA apps serve `convey/static/shell.html` at `/app/{name}/` and their workspace -bytes at `/app/{name}/workspace`. +**Static shell:** Every app serves `convey/static/shell.html` at `/app/{name}/` +and its `workspace.html` bytes at `/app/{name}/workspace`. Keep +`workspace.html` construct-free — no `{{`, `{%`, or `{#`. **Examples:** Browse `solstone/apps/*/app.json` for reference configurations. @@ -634,7 +633,7 @@ See [talent/journal/SKILL.md](../talent/journal/SKILL.md), [CORTEX.md](CORTEX.md ### Global Variables -Defined in `solstone/convey/templates/app.html`: +Set up by the shell runtime (`solstone/convey/static/app.js`): - `window.facetsData` - Array of facet objects `[{name, title, color, emoji}, ...]` - `window.selectedFacet` - Current facet name or null (see Facet Selection below) - `window.appFacetCounts` - Badge counts for current app `{"work": 5, "personal": 3}` (set via route's `facet_counts`) @@ -766,12 +765,10 @@ See `solstone/apps/entities/routes.py` POST handlers - Shows request parsing, va See `solstone/apps/entities/routes.py` - Loads data per-facet when selected, or all facets when null. ### Facet Pill Badges -Pass `facet_counts` dict to `render_template()` to show initial badge counts on facet pills: -```python -facet_counts = {"work": 5, "personal": 3} -return render_template("app.html", facet_counts=facet_counts) +Use the shell runtime to show or update facet pill counts: +```javascript +AppServices.badges.facet.set(facetName, count); ``` -For client-side updates, use `AppServices.badges.facet.set(facetName, count)`. Apps with per-facet counts should compute them from already-loaded data before rendering, or update them client-side as data changes. @@ -836,7 +833,7 @@ Browse `solstone/apps/*/` directories for reference implementations. Apps range - **`solstone/apps/__init__.py`** - App discovery and registry implementation - **`solstone/convey/apps.py`** - Context processors and vendor library helper -- **`solstone/convey/templates/app.html`** - Main app container template +- **`solstone/convey/static/shell.html`** - Static SPA shell served for every app - **`solstone/convey/static/app.js`** - AppServices framework - **`solstone/convey/static/websocket.js`** - WebSocket event system - [../AGENTS.md](../AGENTS.md) - Project development guidelines and standards diff --git a/docs/CONVEY-FRONTEND.md b/docs/CONVEY-FRONTEND.md index ae9b6ec83..26f2e15e9 100644 --- a/docs/CONVEY-FRONTEND.md +++ b/docs/CONVEY-FRONTEND.md @@ -11,11 +11,9 @@ server. ## Architecture: static shell + per-app workspaces -- **One static shell** (`convey/static/shell.html`) is served for every - `/app/{name}` route. The client derives the current app from - `location.pathname` and boots from `GET /api/shell`. During the transitional - conversion, apps opt into this path with `"spa": true` in `app.json`; - unflagged apps continue through the legacy Jinja shell. +- **One static shell** (`convey/static/shell.html`) is served unconditionally for + every `/app/{name}` route. The client derives the current app from + `location.pathname` and boots from `GET /api/shell`. - **Per-app workspace fragments** stay one file per app (`apps/{name}/workspace.html`): markup + ` - {% endif %} - {% endif %} - -{% set body_classes = [] %} -{% if app_registry.apps[app].app_bar %}{% set _ = body_classes.append('has-app-bar') %}{% endif %} -{% if app_registry.apps[app].date_nav_enabled() and day %}{% set _ = body_classes.append('has-date-nav') %}{% endif %} - - - - - - - - -{% include "menu_bar.html" %} - - - - - -{% if app_registry.apps[app].date_nav_enabled() and day %} - {% include "date_nav.html" %} -{% endif %} - - -{% include "status_pane.html" %} -{% include "diagnostic_console.html" %} - - -
- - - - -{% if app_registry.apps[app].app_bar %} - {% include "chat_bar.html" %} -{% endif %} - - -
- {% block workspace_content %} -

{{ app_registry.apps[app].label }}

- {% include app_registry.apps[app].get_workspace_template() %} - {% endblock %} -
- - - - -{% for app_name, app_instance in app_registry.apps.items() %} - {% set background_tpl = app_instance.get_background_template() %} - {% if background_tpl %} - - - {% endif %} -{% endfor %} - - - - - - diff --git a/solstone/convey/templates/chat_bar.html b/solstone/convey/templates/chat_bar.html deleted file mode 100644 index 2ba95e545..000000000 --- a/solstone/convey/templates/chat_bar.html +++ /dev/null @@ -1,61 +0,0 @@ -
- - - - -
- - - -
-
-
- - -
- -
- diff --git a/solstone/convey/templates/date_nav.html b/solstone/convey/templates/date_nav.html deleted file mode 100644 index de0b59cb9..000000000 --- a/solstone/convey/templates/date_nav.html +++ /dev/null @@ -1,127 +0,0 @@ -{# Date Nav - renders below facet bar when app has date_nav enabled #} -{# Both day and app are auto-extracted from URL by context processor #} -{% if day %} -
-
- -
- -
- - -
-
-
- - - -{% endif %} diff --git a/solstone/convey/templates/diagnostic_console.html b/solstone/convey/templates/diagnostic_console.html deleted file mode 100644 index 65276a101..000000000 --- a/solstone/convey/templates/diagnostic_console.html +++ /dev/null @@ -1,24 +0,0 @@ - diff --git a/solstone/convey/templates/menu_bar.html b/solstone/convey/templates/menu_bar.html deleted file mode 100644 index b9114e622..000000000 --- a/solstone/convey/templates/menu_bar.html +++ /dev/null @@ -1,26 +0,0 @@ - - diff --git a/solstone/convey/templates/status_pane.html b/solstone/convey/templates/status_pane.html deleted file mode 100644 index c7e92b479..000000000 --- a/solstone/convey/templates/status_pane.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/solstone/convey/tests/test_static_cache.py b/solstone/convey/tests/test_static_cache.py index 38f483342..c06b7667b 100644 --- a/solstone/convey/tests/test_static_cache.py +++ b/solstone/convey/tests/test_static_cache.py @@ -1,9 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc -import re from datetime import timedelta -from pathlib import Path def test_send_file_max_age_default_configured(convey_env): @@ -20,13 +18,3 @@ def test_static_asset_carries_max_age_and_etag(convey_env): assert resp.status_code == 200 assert "max-age=300" in resp.headers["Cache-Control"] assert resp.headers.get("ETag") - - -def test_head_scripts_all_deferred(): - app_html = Path(__file__).resolve().parents[1] / "templates" / "app.html" - text = app_html.read_text(encoding="utf-8") - - script_tags = re.findall(r"