Simplify Doris Barbell dashboard
Some checks failed
secret-guardrails / gitleaks (push) Has been cancelled
secret-guardrails / artifact-secret-scan (push) Has been cancelled

This commit is contained in:
Fizzlepoof
2026-05-28 20:49:23 +00:00
parent 00100c5cba
commit 64e7ac82cf
4 changed files with 269 additions and 291 deletions

View File

@@ -413,6 +413,79 @@ button:focus, input:focus, select:focus, textarea:focus { outline: 2px solid rgb
padding: 14px;
}
.compact-casefile-header {
padding-bottom: 0.35rem;
}
.compact-title-row {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(220px, 300px);
gap: 16px;
}
.compact-hot-fuzz-art {
min-width: 0;
}
.compact-hero-mark {
max-width: 300px;
margin-left: auto;
}
.compact-page-shell {
padding-bottom: 1.5rem;
}
.compact-panel {
padding: 1rem;
}
.compact-stat-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.85rem;
margin-bottom: 0.9rem;
}
.stat-slab {
display: grid;
gap: 0.3rem;
}
.stat-label,
.section-label {
color: var(--hf-amber);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.72rem;
}
.metric-value-sm {
font-size: 1.5rem;
}
.compact-summary-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
gap: 0.65rem;
}
.compact-summary-list li {
margin: 0;
padding: 0.7rem 0.8rem;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
background: rgba(255, 255, 255, 0.03);
}
.compact-summary-list li + li {
margin-top: 0;
}
.compact-history-block h3 {
margin-bottom: 0.65rem;
}
@media (max-width: 700px) {
.page-header,
.page-shell {
@@ -431,6 +504,18 @@ button:focus, input:focus, select:focus, textarea:focus { outline: 2px solid rgb
flex-basis: 100%;
min-width: 0;
}
.compact-title-row {
grid-template-columns: 1fr;
}
.compact-hero-mark {
margin-left: 0;
}
.compact-stat-grid {
grid-template-columns: 1fr;
}
}
.doris-family-shell .case-nav {

View File

@@ -4,11 +4,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title or 'Doris Barbell' }}</title>
<link rel="stylesheet" href="/static/app.css?v=6">
<link rel="stylesheet" href="/static/app.css?v=7">
</head>
<body class="doris-hot-fuzz doris-family-shell app-barbell">
<div class="incident-tape" aria-hidden="true"></div>
<header class="page-header casefile-header app-casefile app-casefile-barbell">
<header class="page-header casefile-header app-casefile app-casefile-barbell compact-casefile-header">
<div class="film-grain" aria-hidden="true"></div>
<div class="cinematic-glow" aria-hidden="true"></div>
<div class="brand-row">
@@ -24,21 +24,18 @@
<a class="nav-link" href="https://schoolhouse.paccoco.com/">Schoolhouse</a>
</nav>
</div>
<div class="casefile-title-row">
<div>
<div class="casefile-title-row compact-title-row">
<div class="hero-copy">
<p class="eyebrow">Station training desk</p>
<div class="casefile-stamp barbell-evidence-tag">Training Dossier</div>
<h1>Doris Barbell</h1>
<p class="lede">Reps, weight, BMI, and body metrics now look like a proper performance dossier instead of generic gym CRUD.</p>
<p class="lede">Log lifts, body weight, and routines.</p>
<div class="brand-badges" aria-label="Theme badges">
<span class="pill badge-hotfuzz">For the Greater Good</span>
<span class="pill">Evidence locker: reps</span>
<span class="pill">Case type: Training Dossier</span>
</div>
</div>
<div class="art-stack">
<div class="hot-fuzz-art" aria-hidden="true">
<svg viewBox="0 0 320 220" class="poster-illustration" role="presentation">
<div class="hot-fuzz-art compact-hot-fuzz-art" aria-hidden="true">
<svg viewBox="0 0 320 140" class="poster-illustration compact-hero-mark" role="presentation">
<defs>
<linearGradient id="barbell-sunset" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#f2c14e"></stop>
@@ -46,59 +43,23 @@
<stop offset="100%" stop-color="#910f3f"></stop>
</linearGradient>
</defs>
<rect x="8" y="8" width="304" height="204" rx="24" class="poster-frame"></rect>
<circle cx="228" cy="72" r="58" class="poster-halo"></circle>
<path d="M18 170 L112 100 L134 124 L40 198 Z" class="poster-tape tape-left"></path>
<path d="M202 82 L302 32 L314 60 L214 112 Z" class="poster-tape tape-right"></path>
<path d="M112 180 C110 150 116 126 132 110 C140 100 148 94 154 90 C160 94 168 100 176 110 C192 126 198 150 196 180 Z" class="constable-silhouette lead"></path>
<path d="M174 184 C172 154 178 132 194 118 C202 110 210 104 218 100 C226 104 234 110 242 118 C258 132 264 154 262 184 Z" class="constable-silhouette partner"></path>
<circle cx="84" cy="60" r="28" class="swan-stamp"></circle>
<path d="M76 64 C80 54 91 48 101 53 C93 52 88 57 89 63 C90 69 101 68 103 76 C94 78 83 76 78 69 L71 76 L66 71 L74 64 Z" class="swan-mark"></path>
<rect x="120" y="88" width="88" height="38" rx="8" class="evidence-card"></rect>
<path d="M130 98 h58 M130 108 h42 M130 118 h32" class="evidence-lines"></path>
<circle cx="88" cy="88" r="8" class="marker-dot"></circle>
<circle cx="100" cy="104" r="6" class="marker-dot marker-dot-alt"></circle>
<path d="M96 94 L122 110 L152 96" class="map-thread"></path>
<path d="M210 88 C226 80 242 80 254 88" class="beacon-arc"></path>
<rect x="122" y="110" width="86" height="8" rx="4" class="barbell-bar"></rect>
<circle cx="120" cy="114" r="16" class="plate-outer"></circle>
<circle cx="120" cy="114" r="8" class="plate-inner"></circle>
<circle cx="210" cy="114" r="16" class="plate-outer"></circle>
<circle cx="210" cy="114" r="8" class="plate-inner"></circle>
<rect x="216" y="126" width="38" height="16" rx="6" class="radio-body"></rect>
<rect x="232" y="110" width="8" height="16" rx="4" class="radio-antenna"></rect>
<text x="160" y="204" class="poster-callout">TRAINING DOSSIER</text>
<rect x="8" y="8" width="304" height="124" rx="24" class="poster-frame"></rect>
<circle cx="250" cy="45" r="34" class="poster-halo"></circle>
<path d="M34 104 L86 58 L102 74 L50 118 Z" class="poster-tape tape-left"></path>
<path d="M218 44 L292 18 L300 38 L226 66 Z" class="poster-tape tape-right"></path>
<path d="M112 106 C112 82 118 64 132 52 C140 44 147 39 154 36 C161 39 168 44 176 52 C190 64 196 82 196 106 Z" class="constable-silhouette lead"></path>
<path d="M168 108 C170 86 178 70 192 60 C201 53 210 48 218 45 C226 48 235 53 242 60 C255 70 262 86 260 108 Z" class="constable-silhouette partner"></path>
<circle cx="76" cy="44" r="20" class="swan-stamp"></circle>
<path d="M71 47 C74 40 82 35 90 39 C84 39 80 43 81 48 C82 53 90 53 92 58 C85 60 77 58 73 53 L68 58 L64 55 L70 49 Z" class="swan-mark"></path>
<rect x="114" y="62" width="92" height="10" rx="5" class="barbell-bar"></rect>
<circle cx="112" cy="67" r="16" class="plate-outer"></circle>
<circle cx="112" cy="67" r="8" class="plate-inner"></circle>
<circle cx="208" cy="67" r="16" class="plate-outer"></circle>
<circle cx="208" cy="67" r="8" class="plate-inner"></circle>
<path d="M88 76 L120 68 L154 82" class="map-thread"></path>
<text x="160" y="118" class="poster-callout">TRAINING DOSSIER</text>
</svg>
</div>
<div class="evidence-contact-grid">
<section class="evidence-note-card case-contact-sheet" aria-label="Training caseboard details">
<div class="case-contact-top">
<span class="case-contact-kicker">Station drill board</span>
<span class="case-contact-badge">Cadet fitness</span>
</div>
<svg viewBox="0 0 320 160" class="contact-sheet-illustration" role="presentation">
<rect x="12" y="14" width="116" height="132" rx="16" class="contact-frame"></rect>
<rect x="24" y="30" width="92" height="54" rx="12" class="contact-photo"></rect>
<path d="M30 96 h80 M30 108 h66 M30 120 h48" class="contact-lines"></path>
<rect x="146" y="24" width="78" height="44" rx="10" class="contact-photo alt"></rect>
<rect x="160" y="84" width="112" height="52" rx="12" class="contact-photo alt"></rect>
<circle cx="250" cy="40" r="16" class="contact-marker"></circle>
<path d="M132 94 C152 82 176 80 194 88 C208 94 228 98 250 96" class="contact-thread"></path>
<text x="70" y="60" class="contact-label">RPE</text>
<text x="186" y="52" class="contact-label">PACE</text>
<text x="218" y="114" class="contact-label">PLATE</text>
</svg>
<ul class="case-contact-list">
<li>Clipboard-style slips, radio cues, and drill-board labels make the page read like a police training dossier instead of generic fitness software.</li>
<li>The photo still remains for texture, but the authored graphics now carry the theme.</li>
</ul>
</section>
<figure class="photo-evidence-card hero-photo-card supporting-photo-card">
<img src="/static/images/hotfuzz-training-dossier.jpg" alt="Sepia-toned training photo with dumbbells, weight plates, and a handwritten workout clipboard." loading="eager">
<figcaption>Training still · source logged in docs</figcaption>
</figure>
</div>
</div>
</div>
<nav class="case-nav" aria-label="Doris Barbell sections">
<a class="case-nav-link active" href="/#body-metrics">Body metrics</a>
@@ -107,44 +68,7 @@
<a class="case-nav-link" href="/#recent-workouts">Workout reports</a>
</nav>
</header>
<main class="page-shell">
<section class="family-portal evidence-board casefile-directory">
<div class="section-heading">
<div>
<p class="section-label">Singular front door</p>
<h2>Doris family directory</h2>
</div>
<span class="pill">Casefile directory</span>
</div>
<p class="muted case-legend">One shell, separate runtimes. Training reports live here, but the rest of the Doris family is one jump away.</p>
<div class="family-directory-grid">
<a class="family-app-card current-desk" href="https://gym.paccoco.com/">
<span class="family-app-kicker">Current desk</span>
<strong>Doris Barbell</strong>
<small>Body metrics, routines, workouts, and progression dossiers.</small>
</a>
<a class="family-app-card" href="http://10.5.30.7:8787/">
<span class="family-app-kicker">Operator portal</span>
<strong>Doris Dashboard</strong>
<small>Canonical front door for the whole Doris family.</small>
</a>
<a class="family-app-card" href="http://10.5.30.7:8787/services.html">
<span class="family-app-kicker">Switchboard</span>
<strong>Services Directory</strong>
<small>Dense launch wall for the wider homelab stack.</small>
</a>
<a class="family-app-card" href="http://10.5.30.7:8092/">
<span class="family-app-kicker">Pantry desk</span>
<strong>Doris Kitchen</strong>
<small>Recipe leads, repair queue, and planning board.</small>
</a>
<a class="family-app-card" href="https://schoolhouse.paccoco.com/">
<span class="family-app-kicker">Records desk</span>
<strong>Doris Schoolhouse</strong>
<small>Assignments, recordings, and school intake packets.</small>
</a>
</div>
</section>
<main class="page-shell compact-page-shell">
{% block content %}{% endblock %}
</main>
</body>

View File

@@ -1,51 +1,66 @@
{% extends 'base.html' %}
{% block content %}
<section id="body-metrics" class="grid cols-2 evidence-board dossier-grid">
<article class="card evidence-card">
<section id="body-metrics" class="grid cols-2 dossier-grid">
<article class="card evidence-card compact-panel">
<div class="case-legend">
<span class="section-label">Training floor board</span>
<span class="pill">Training Dossier</span>
<span class="pill">John</span>
</div>
<h2>Current body metrics</h2>
<div class="grid dossier-grid">
{% for user in summary.users %}
<section class="evidence-slab">
<h3>{{ user.display_name }} {% if user.is_stub %}<span class="badge">stub</span>{% endif %}</h3>
{% for user in summary.users if user.id == 'john' %}
<div class="compact-stat-grid">
<section class="evidence-slab stat-slab">
<span class="stat-label">Weight</span>
<p class="metric-value">{{ user.current_weight_lbs if user.current_weight_lbs is not none else '—' }}{% if user.current_weight_lbs is not none %} lb{% endif %}</p>
<p class="muted">BMI: {{ '%.1f'|format(user.current_bmi) if user.current_bmi is not none else 'Not set yet' }}</p>
<p class="muted">System: {{ user.measurement_system }}</p>
</section>
<section class="evidence-slab stat-slab">
<span class="stat-label">BMI</span>
<p class="metric-value metric-value-sm">{{ '%.1f'|format(user.current_bmi) if user.current_bmi is not none else '—' }}</p>
</section>
</div>
{% if user.latest_measurements %}
<p class="muted">
Waist {{ user.latest_measurements.waist_inches or '—' }} in ·
Hips {{ user.latest_measurements.hip_inches or '—' }} in ·
Chest {{ user.latest_measurements.chest_inches or '—' }} in ·
Neck {{ user.latest_measurements.neck_inches or '—' }} in
</p>
<ul class="list compact-summary-list">
<li>Waist {{ user.latest_measurements.waist_inches or '—' }} in</li>
<li>Chest {{ user.latest_measurements.chest_inches or '—' }} in</li>
<li>Hips {{ user.latest_measurements.hip_inches or '—' }} in</li>
<li>Neck {{ user.latest_measurements.neck_inches or '—' }} in</li>
</ul>
{% else %}
<p class="muted">No body measurements logged yet.</p>
{% endif %}
</section>
{% endfor %}
</div>
</article>
<article class="card evidence-card">
<article id="routine-board" class="card evidence-board compact-panel">
<div class="case-legend">
<span class="section-label">Case priorities</span>
<span class="pill">Operator notes</span>
<span class="section-label">Current split</span>
<span class="pill">Routine board</span>
</div>
<h2>V1 focus</h2>
<ul class="list case-list">
<li>Structured workout logging with routines and exercises.</li>
<li>Body-weight, BMI, waist/hip/chest/neck history in imperial units.</li>
<li>Recent workouts, volume trends, and simple PR tracking.</li>
<h2>Current routine board</h2>
<ul class="list compact-summary-list">
<li>Routines: {{ summary.counts.routines }}</li>
<li>Exercise templates: {{ summary.counts.exercise_templates }}</li>
<li>Workout sessions: {{ summary.counts.workout_sessions }}</li>
</ul>
{% if summary.routines %}
<div class="history-block compact-history-block">
<ul class="list">
{% for routine in summary.routines %}
<li>
<strong>{{ routine.name }}</strong>
<p class="muted">{{ routine.exercises|length }} exercises</p>
<p><a class="nav-link" href="/routines/{{ routine.id }}/log">Open log sheet</a></p>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</article>
</section>
<section id="weight-log" class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Intake docket</span><span class="pill">Weight log</span></div>
<h2>Log weight</h2>
<form class="stack-form intake-docket" method="post" action="/log/weight">
@@ -74,7 +89,7 @@
</form>
</article>
<article class="card evidence-board">
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Intake docket</span><span class="pill">Measurements</span></div>
<h2>Log body measurements</h2>
<form class="stack-form intake-docket" method="post" action="/log/measurements">
@@ -104,109 +119,8 @@
</article>
</section>
<section class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">Exercise library</span><span class="pill">Template intake</span></div>
<h2>Add exercise template</h2>
<form class="stack-form intake-docket" method="post" action="/templates">
<label>Name
<input type="text" name="name" placeholder="Bench Press" required>
</label>
<div class="grid cols-2 compact-grid">
<label>Primary muscle
<input type="text" name="primary_muscle" placeholder="Chest">
</label>
<label>Equipment
<input type="text" name="equipment" placeholder="Barbell">
</label>
</div>
<label>Notes
<textarea name="notes" rows="3" placeholder="Technique cue, setup reminder, variation notes."></textarea>
</label>
<button type="submit">Add template</button>
</form>
</article>
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">Routine intake</span><span class="pill">Draft split</span></div>
<h2>Build routine draft</h2>
<form class="stack-form intake-docket" method="post" action="/routines">
<input type="hidden" name="user_id" value="john">
<label>Routine name
<input type="text" name="name" placeholder="Push Day Draft" required>
</label>
<label>Exercise lines
<textarea name="exercise_lines" rows="6" placeholder="Bench Press|4|6-8&#10;Incline Dumbbell Press|3|8-10&#10;Triceps Pushdown|3|12-15" required></textarea>
</label>
<p class="hint">Format: exercise | target sets | target reps | optional notes</p>
<label>Notes
<textarea name="notes" rows="2" placeholder="Temporary until the real split gets entered."></textarea>
</label>
<button type="submit">Save routine draft</button>
</form>
</article>
</section>
<section id="recent-workouts" class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">Workout report</span><span class="pill">Quick log</span></div>
<h2>Quick log workout</h2>
<form class="stack-form intake-docket" method="post" action="/workouts/quick-log">
<input type="hidden" name="user_id" value="john">
<label>Workout name
<input type="text" name="routine_name" placeholder="Push Day" required>
</label>
<label>Performed at
<input type="datetime-local" name="performed_at" required>
</label>
<label>Exercise lines
<textarea name="exercise_lines" rows="6" placeholder="Bench Press|8@135, 8@145&#10;Incline Dumbbell Press|10@50, 8@55|Last set close to failure" required></textarea>
</label>
<p class="hint">Format: exercise | reps@weight, reps@weight | optional notes</p>
<label>Notes
<textarea name="notes" rows="2" placeholder="Energy, pain, wins, misses."></textarea>
</label>
<button type="submit">Save workout</button>
</form>
</article>
<article id="routine-board" class="card evidence-board">
<div class="case-legend"><span class="section-label">Current split</span><span class="pill">Routine board</span></div>
<h2>Current routine board</h2>
<p class="muted">John's current five-day split is now loaded as the default routine library with a default prescription of 3 sets of 10 reps. Weights are still TBD.</p>
<ul class="list case-list">
<li>Exercise templates: {{ summary.counts.exercise_templates }}</li>
<li>Routines: {{ summary.counts.routines }}</li>
</ul>
{% if summary.routines %}
<div class="history-block">
<h3>Loaded routine days</h3>
<ul class="list">
{% for routine in summary.routines %}
<li>
<strong>{{ routine.name }}</strong>
<p class="muted">{{ routine.exercises|length }} exercises{% if routine.notes %} · {{ routine.notes }}{% endif %}</p>
<p><a class="nav-link" href="/routines/{{ routine.id }}/log">Open log sheet</a></p>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if summary.exercise_templates %}
<div class="history-block">
<h3>Template snapshot</h3>
<ul class="list">
{% for template in summary.exercise_templates %}
<li>{{ template.name }}{% if template.equipment %} · {{ template.equipment }}{% endif %}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</article>
</section>
<section class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Recent logs</span><span class="pill">Session reports</span></div>
<h2>Recent workouts</h2>
{% if john_workout_history %}
@@ -223,50 +137,7 @@
{% endif %}
</article>
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">History snapshot</span><span class="pill">John file</span></div>
<h2>John history snapshot</h2>
<div class="history-block">
<h3>Recent weights</h3>
{% if john_weight_history %}
<ul class="list">
{% for entry in john_weight_history %}
<li>{{ entry.recorded_at }} · {{ entry.weight_lbs }} lb{% if entry.bmi is not none %} · BMI {{ '%.1f'|format(entry.bmi) }}{% endif %}</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">No weight entries yet.</p>
{% endif %}
</div>
<div class="history-block">
<h3>Recent measurements</h3>
{% if john_measurement_history %}
<ul class="list">
{% for entry in john_measurement_history %}
<li>{{ entry.recorded_at }} · Waist {{ entry.waist_inches or '—' }} · Chest {{ entry.chest_inches or '—' }} · Hips {{ entry.hip_inches or '—' }} · Neck {{ entry.neck_inches or '—' }}</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">No measurement entries yet.</p>
{% endif %}
</div>
</article>
</section>
<section class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">Current counts</span><span class="pill">Inventory</span></div>
<h2>Current counts</h2>
<ul class="list case-list">
<li>Workout sessions: {{ summary.counts.workout_sessions }}</li>
<li>Weight entries: {{ summary.counts.weight_entries }}</li>
<li>Measurement entries: {{ summary.counts.measurement_entries }}</li>
<li>Exercise templates: {{ summary.counts.exercise_templates }}</li>
<li>Routines: {{ summary.counts.routines }}</li>
</ul>
</article>
<article class="card evidence-board">
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Exercise bests</span><span class="pill">PR board</span></div>
<h2>Exercise bests</h2>
{% if john_progression %}
@@ -285,14 +156,108 @@
</section>
<section class="grid cols-2 dossier-grid">
<article class="card evidence-board">
<div class="case-legend"><span class="section-label">Next likely build</span><span class="pill">Open casework</span></div>
<h2>Next likely build</h2>
<ul class="list case-list">
<li>Replace placeholder routines with Johns real day-by-day program tomorrow.</li>
<li>Routine-specific workout entry flow driven directly from the saved split.</li>
<li>Exercise progression and PR views.</li>
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Library tools</span><span class="pill">Templates</span></div>
<h2>Add exercise template</h2>
<form class="stack-form intake-docket" method="post" action="/templates">
<label>Name
<input type="text" name="name" placeholder="Bench Press" required>
</label>
<div class="grid cols-2 compact-grid">
<label>Primary muscle
<input type="text" name="primary_muscle" placeholder="Chest">
</label>
<label>Equipment
<input type="text" name="equipment" placeholder="Barbell">
</label>
</div>
<label>Notes
<textarea name="notes" rows="2" placeholder="Technique cue or setup reminder."></textarea>
</label>
<button type="submit">Add template</button>
</form>
{% if summary.exercise_templates %}
<div class="history-block compact-history-block">
<h3>Template snapshot</h3>
<ul class="list">
{% for template in summary.exercise_templates %}
<li>{{ template.name }}{% if template.equipment %} · {{ template.equipment }}{% endif %}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</article>
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Workout report</span><span class="pill">Quick log</span></div>
<h2>Quick log workout</h2>
<form class="stack-form intake-docket" method="post" action="/workouts/quick-log">
<input type="hidden" name="user_id" value="john">
<label>Workout name
<input type="text" name="routine_name" placeholder="Push Day" required>
</label>
<label>Performed at
<input type="datetime-local" name="performed_at" required>
</label>
<label>Exercise lines
<textarea name="exercise_lines" rows="5" placeholder="Bench Press|8@135, 8@145&#10;Incline Dumbbell Press|10@50, 8@55|Last set close to failure" required></textarea>
</label>
<p class="hint">Format: exercise | reps@weight, reps@weight | optional notes</p>
<label>Notes
<textarea name="notes" rows="2" placeholder="Energy, pain, wins, misses."></textarea>
</label>
<button type="submit">Save workout</button>
</form>
</article>
</section>
<section class="grid cols-2 dossier-grid">
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">Routine tools</span><span class="pill">Draft split</span></div>
<h2>Build routine draft</h2>
<form class="stack-form intake-docket" method="post" action="/routines">
<input type="hidden" name="user_id" value="john">
<label>Routine name
<input type="text" name="name" placeholder="Push Day Draft" required>
</label>
<label>Exercise lines
<textarea name="exercise_lines" rows="5" placeholder="Bench Press|4|6-8&#10;Incline Dumbbell Press|3|8-10&#10;Triceps Pushdown|3|12-15" required></textarea>
</label>
<p class="hint">Format: exercise | target sets | target reps | optional notes</p>
<label>Notes
<textarea name="notes" rows="2" placeholder="Optional notes."></textarea>
</label>
<button type="submit">Save routine draft</button>
</form>
</article>
<article class="card evidence-board compact-panel">
<div class="case-legend"><span class="section-label">History snapshot</span><span class="pill">John file</span></div>
<h2>Recent body history</h2>
<div class="history-block compact-history-block">
<h3>Recent weights</h3>
{% if john_weight_history %}
<ul class="list">
{% for entry in john_weight_history %}
<li>{{ entry.recorded_at }} · {{ entry.weight_lbs }} lb{% if entry.bmi is not none %} · BMI {{ '%.1f'|format(entry.bmi) }}{% endif %}</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">No weight entries yet.</p>
{% endif %}
</div>
<div class="history-block compact-history-block">
<h3>Recent measurements</h3>
{% if john_measurement_history %}
<ul class="list">
{% for entry in john_measurement_history %}
<li>{{ entry.recorded_at }} · Waist {{ entry.waist_inches or '—' }} · Chest {{ entry.chest_inches or '—' }} · Hips {{ entry.hip_inches or '—' }} · Neck {{ entry.neck_inches or '—' }}</li>
{% endfor %}
</ul>
{% else %}
<p class="muted">No measurement entries yet.</p>
{% endif %}
</div>
</article>
</section>
{% endblock %}

View File

@@ -379,12 +379,16 @@ def test_homepage_renders_core_sections_and_forms(tmp_path: Path) -> None:
assert response.status_code == 200
body = response.text
assert 'Doris Barbell' in body
assert 'Log lifts, body weight, and routines.' in body
assert 'Recent workouts' in body
assert 'Current body metrics' in body
assert 'Log weight' in body
assert 'Add exercise template' in body
assert 'Current routine board' in body
assert 'Monday: Chest' in body
assert 'Doris family directory' not in body
assert 'V1 focus' not in body
assert 'Next likely build' not in body
def test_seeded_routine_can_render_prefilled_log_sheet(tmp_path: Path) -> None: