{#- Vendored interaction stack (no CDN, no build step): htmx serves fragments, Alpine owns client state. All four load deferred and execute in this order before DOMContentLoaded: htmx core, its alpine-morph extension (defineExtension needs the htmx global), Alpine's morph plugin (registers Alpine.morph on alpine:init), Alpine core. -#} /* Workspace stickiness: the store selection rides the URL (?store=, the FR-003 seam), which a typed URL or new tab drops — so, like the theme, the last explicit selection is remembered (localStorage "cairn-store") and a bare visit redirects to it. Until the user picks a workspace, nothing is stored and bare URLs keep the launch store, byte-identical. A remembered key that no longer names a populated store (the workspace was deleted) is NOT redirected to — that would poison every future visit with the missing-DB page — it is forgotten and the launch store serves; the valid key set rides this tag's data-valid-stores attribute, server-rendered from the same enumerate_stores rows the selector lists. */ (function () { "use strict"; var STORAGE_KEY = "cairn-store"; var url = new URL(window.location.href); var selected = (url.searchParams.get("store") || "").trim(); try { if (selected) { window.localStorage.setItem(STORAGE_KEY, selected); } else { var remembered = window.localStorage.getItem(STORAGE_KEY); var valid = null; var validRaw = document.currentScript ? document.currentScript.getAttribute("data-valid-stores") : null; if (validRaw) { try { valid = JSON.parse(validRaw); } catch (err) { valid = null; /* unparsable set: redirect as before */ } } var known = valid === null || valid.indexOf(remembered) !== -1; if (remembered && known) { url.searchParams.set("store", remembered); window.location.replace( url.pathname + url.search + url.hash ); } else if (remembered) { window.localStorage.removeItem(STORAGE_KEY); } } } catch (err) { /* storage unavailable: the selection stays URL-only */ } })(); {%- set shell_ctx = shell if shell is defined else none %} {%- set nav_sections = (shell_ctx.nav.sections if shell_ctx else none) or [] %} {%- set selector = (shell_ctx.selector if shell_ctx else none) %} {%- set palette = (shell_ctx.palette if shell_ctx else none) %}
{#- The topbar label derives from the path segment ("/" -> Overview); views may override via the view_title block. -#}
{% block view_title %}{{ 'Overview' if path == '/' else (path.strip('/').split('/')[0] | capitalize) }}{% endblock %}
{#- Global workspace selector (FR-003 seam stays the URL param): switching rewrites ?store= on the current URL and reloads, so every tab shows the selected workspace. shell.js owns the change behavior; the options are server-rendered. -#} {% if selector %} {% endif %} {% if palette %} {% endif %}
{% include "_embed_banner.html" %} {% block content %}{% endblock %}
{% if palette %} {#- Command palette: a native (showModal() provides the focus trap and Esc-to-close) driven by the Alpine "palette" component in shell.js — Ctrl/Cmd+K toggle, active-row highlight, focus restored to the launching element on close. Rows come from two sources: the seed JSON below draws the initial unfiltered list the moment the palette opens, and typing re-fetches filtered rows as an htmx fragment from /palette/results (the same seed composition plus the /graph/suggest symbol matches, decided server-side). The dialog sits at body level so live-refresh region swaps never touch it. -#}

    ↑ ↓ navigate · ↵ open · esc close

    {% endif %} {% block scripts %}{% endblock %}