fix: harden html responses and cache chat sidebar data

Use strict template response handling and sanitized template user context, then add an in-process conversation archive cache with mutation-driven invalidation for chat sidebar renders.
This commit is contained in:
Per Stark
2026-02-14 17:47:14 +01:00
parent a3f207beb1
commit f93c06b347
12 changed files with 173 additions and 60 deletions

View File

@@ -13,9 +13,9 @@
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">API Key</div>
{% block api_key_section %}
{% if user.api_key %}
{% if api_key %}
<div class="relative">
<input id="api_key_input" type="text" name="api_key" value="{{ user.api_key }}"
<input id="api_key_input" type="text" name="api_key" value="{{ api_key }}"
class="nb-input w-full pr-14" disabled />
<button type="button" id="copy_api_key_btn" onclick="copy_api_key()"
class="absolute inset-y-0 right-0 flex items-center px-2 nb-btn btn-sm" aria-label="Copy API key"
@@ -48,9 +48,10 @@
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Timezone</div>
{% block timezone_section %}
{% set active_timezone = selected_timezone|default(user.timezone) %}
<select name="timezone" class="nb-select w-full" hx-patch="/update-timezone" hx-swap="outerHTML">
{% for tz in timezones %}
<option value="{{ tz }}" {% if tz==user.timezone %}selected{% endif %}>{{ tz }}</option>
<option value="{{ tz }}" {% if tz==active_timezone %}selected{% endif %}>{{ tz }}</option>
{% endfor %}
</select>
{% endblock %}
@@ -59,13 +60,14 @@
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Theme</div>
{% block theme_section %}
{% set active_theme = selected_theme|default(user.theme) %}
<select name="theme" class="nb-select w-full" hx-patch="/update-theme" hx-swap="outerHTML">
{% for option in theme_options %}
<option value="{{ option }}" {% if option==user.theme %}selected{% endif %}>{{ option }}</option>
<option value="{{ option }}" {% if option==active_theme %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
<script>
document.documentElement.setAttribute('data-theme-preference', '{{ user.theme }}');
document.documentElement.setAttribute('data-theme-preference', '{{ active_theme }}');
</script>
{% endblock %}
</label>

View File

@@ -7,4 +7,5 @@
{% block auth_content %}
{% endblock %}
</div>
<div id="toast-container" class="fixed bottom-4 right-4 z-50 space-y-2"></div>
{% endblock %}

View File

@@ -6,7 +6,7 @@
</div>
<div class="u-hairline mb-3"></div>
<form hx-post="/signin" hx-target="#login-result" class="flex flex-col gap-2">
<form hx-post="/signin" hx-swap="none" class="flex flex-col gap-2">
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
<input name="email" type="email" placeholder="Email" class="nb-input w-full validator" required />
@@ -19,8 +19,6 @@
minlength="8" />
</label>
<div class="mt-1 text-error" id="login-result"></div>
<div class="form-control mt-1">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox" name="remember_me" class="nb-checkbox" />

View File

@@ -11,7 +11,7 @@
</div>
<div class="u-hairline mb-3"></div>
<form hx-post="/signup" hx-target="#signup-result" class="flex flex-col gap-4">
<form hx-post="/signup" hx-swap="none" class="flex flex-col gap-4">
<label class="w-full">
<div class="text-xs uppercase tracking-wide opacity-70 mb-1">Email</div>
<input type="email" placeholder="Email" name="email" required class="nb-input w-full validator" />
@@ -31,7 +31,6 @@
</p>
</label>
<div class="mt-2 text-error" id="signup-result"></div>
<div class="form-control mt-1">
<button id="submit-btn" class="nb-btn nb-cta w-full">Create Account</button>
</div>

View File

@@ -0,0 +1 @@
<a class="btn btn-primary" hx-get="/ingest-form" hx-target="#modal" hx-swap="innerHTML">Add Content</a>