feat: add base shell, notes panel, back-navigation with stale propagation

Implements Task 4: base.html DaisyUI/Alpine shell, notes autosave panel,
nav.py phase switching with downstream stale marking, notes.py save/get
endpoints, state debug endpoint, and stub /triage route for test support.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 16:08:21 +02:00
parent 203737cc3f
commit bcfee45bd7
6 changed files with 235 additions and 3 deletions
@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html data-theme="forest" lang="en">
<head>
<meta charset="UTF-8">
<title>travel-memories</title>
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
</head>
<body class="min-h-screen bg-base-200">
<!-- Navbar -->
<div class="navbar bg-base-100 shadow-sm sticky top-0 z-40">
<div class="navbar-start px-4 font-bold text-lg">travel-memories</div>
<div class="navbar-center">
<ul class="steps">
{% set phases = [('','Album'),('triage','Triage'),('curate','Curate'),('group','Group'),('write','Write'),('export','Export')] %}
{% for key, label in phases %}
<li class="step {% if current_phase == key %}step-primary{% endif %}
{% if key in phase_stale %}step-warning{% endif %}">
{% if album_id %}
<a hx-post="/nav/phase" hx-vals='{"album_id":"{{ album_id }}","target_phase":"{{ key }}"}' href="/{{ key }}{% if album_id %}?album_id={{ album_id }}{% endif %}">{{ label }}</a>
{% else %}{{ label }}{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div class="navbar-end px-4">
{% if album_id %}
<button class="btn btn-ghost btn-sm" @click="open = !open" x-data>📝 Notes</button>
{% endif %}
</div>
</div>
<!-- Stale warning -->
{% if current_phase in phase_stale %}
<div class="alert alert-warning rounded-none" id="stale-banner">
<span>You changed earlier decisions — review this phase before exporting.</span>
<form method="post" action="/nav/dismiss-stale">
<input type="hidden" name="album_id" value="{{ album_id }}">
<input type="hidden" name="phase" value="{{ current_phase }}">
<button class="btn btn-xs">Dismiss</button>
</form>
</div>
{% endif %}
<!-- Body with notes drawer -->
<div class="flex relative" x-data="notesApp('{{ notes_content | e }}', '{{ album_id }}')">
<div class="flex-1 min-w-0 transition-all" :class="open ? 'mr-80' : ''">
{% block content %}{% endblock %}
</div>
<!-- Notes panel -->
<div class="fixed right-0 top-16 h-[calc(100vh-4rem)] w-80 bg-base-100 shadow-2xl p-4 flex flex-col transition-transform z-30"
:class="open ? 'translate-x-0' : 'translate-x-full'" id="notes-panel">
<h3 class="font-bold text-base mb-2">Notes</h3>
<textarea class="textarea textarea-bordered flex-1 resize-none text-sm"
x-model="notes"
@input="scheduleAutosave()"
placeholder="Jot down memories at any time…"></textarea>
<div class="text-xs text-right mt-1 opacity-60" x-text="status"></div>
</div>
</div>
<script src="/static/app.js"></script>
{% block extra_scripts %}{% endblock %}
</body>
</html>