feat: Self-Coding Dashboard HTMX Templates

Add complete UI for self-coding dashboard:

Templates:
- self_coding.html - Main dashboard page with layout
- partials/self_coding_stats.html - Stats cards (total, success rate, etc)
- partials/journal_entries.html - List of modification attempts
- partials/journal_entry_detail.html - Expanded view of single attempt
- partials/execute_form.html - Task execution form
- partials/execute_result.html - Execution result display
- partials/error.html - Error message display

Features:
- HTMX-powered dynamic updates
- Real-time journal filtering (all/success/failure)
- Modal dialog for task execution
- Responsive Bootstrap 5 styling
- Automatic refresh after successful execution
This commit is contained in:
Alexander Payne
2026-02-26 12:15:30 -05:00
parent cb70cb392a
commit e81be8aed7
7 changed files with 483 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{# Error partial #}
<div class="alert alert-danger mb-0">
<div class="d-flex align-items-center gap-2">
<span>⚠️</span>
<span>{{ message }}</span>
</div>
</div>

View File

@@ -0,0 +1,45 @@
{# Execute task form partial #}
<form hx-post="/self-coding/execute" hx-target="#execute-result" hx-indicator="#execute-loading">
<div class="mb-3">
<label for="task-description" class="form-label">Task Description</label>
<textarea
class="form-control form-control-sm bg-dark text-light border-secondary"
id="task-description"
name="task_description"
rows="4"
placeholder="Describe what you want Timmy to do...
Example: Add error handling to the /health endpoint that returns 503 when Ollama is unreachable."
required
></textarea>
<div class="form-text">
Be specific. Include what to change and what the expected behavior should be.
</div>
</div>
<div class="alert alert-warning d-flex align-items-start gap-2 py-2">
<span>⚠️</span>
<small>
<strong>Warning:</strong> This will modify source code. Changes will be tested and committed.
Safety constraints: max 3 files, only files with tests, protected files cannot be modified.
</small>
</div>
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="document.getElementById('execute-modal').close()">
Cancel
</button>
<button type="submit" class="btn btn-sm btn-primary" onclick="document.getElementById('execute-modal').close()">
Execute Task
</button>
</div>
</form>
<div id="execute-loading" class="htmx-indicator mt-3">
<div class="d-flex align-items-center gap-2 text-muted">
<div class="spinner-border spinner-border-sm" role="status"></div>
<small>Executing self-edit task... This may take a few minutes.</small>
</div>
</div>
<div id="execute-result" class="mt-3"></div>

View File

@@ -0,0 +1,58 @@
{# Execute task result partial #}
<div class="alert {% if result.success %}alert-success{% else %}alert-danger{% endif %} mb-0">
<div class="d-flex align-items-start gap-2">
<span class="fs-5">{% if result.success %}✅{% else %}❌{% endif %}</span>
<div>
<h6 class="alert-heading mb-1">
{% if result.success %}Success!{% else %}Failed{% endif %}
</h6>
<p class="mb-0 small">{{ result.message }}</p>
{% if result.success %}
{% if result.files_modified %}
<div class="mt-2">
<small class="text-muted">Files modified:</small>
<ul class="list-unstyled mb-0 small">
{% for file in result.files_modified %}
<li><code>{{ file }}</code></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if result.commit_hash %}
<div class="mt-2 small">
<span class="text-muted">Commit:</span>
<code>{{ result.commit_hash[:8] }}</code>
</div>
{% endif %}
{% if result.attempt_id %}
<div class="mt-2">
<a href="/self-coding#journal-{{ result.attempt_id }}" class="btn btn-sm btn-outline-success">
View in Journal
</a>
</div>
{% endif %}
{% else %}
{% if result.test_results %}
<div class="mt-2">
<small class="text-muted">Test output:</small>
<pre class="small bg-black bg-opacity-25 p-2 rounded mb-0 mt-1 overflow-auto" style="max-height: 150px;"><code>{{ result.test_results[:500] }}{% if result.test_results|length > 500 %}...{% endif %}</code></pre>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
{# Refresh journal and stats after execution #}
{% if result.success %}
<script>
// Refresh journal and stats after successful execution
setTimeout(() => {
htmx.ajax('GET', '/self-coding/journal', { target: '#journal-container' });
htmx.ajax('GET', '/self-coding/stats', { target: '#stats-container' });
}, 500);
</script>
{% endif %}

View File

@@ -0,0 +1,64 @@
{# Journal entries list partial #}
{% if entries %}
<div class="list-group list-group-flush">
{% for entry in entries %}
<div class="list-group-item journal-entry {{ entry.outcome.value }} p-3"
hx-get="/self-coding/journal/{{ entry.id }}/detail"
hx-target="#journal-detail-{{ entry.id }}"
hx-swap="innerHTML"
style="cursor: pointer;">
<div class="d-flex justify-content-between align-items-start mb-2">
<div class="d-flex align-items-center gap-2">
{# Outcome icon #}
{% if entry.outcome.value == 'success' %}
<span class="badge bg-success"></span>
{% elif entry.outcome.value == 'failure' %}
<span class="badge bg-danger"></span>
{% else %}
<span class="badge bg-warning text-dark"></span>
{% endif %}
<span class="text-muted small">
#{{ entry.id }}
</span>
</div>
<small class="text-muted">
{{ entry.timestamp.strftime('%Y-%m-%d %H:%M') if entry.timestamp else 'Unknown' }}
</small>
</div>
<p class="mb-1 fw-medium">{{ entry.task_description }}</p>
<div class="d-flex justify-content-between align-items-center">
<div class="small text-muted">
{% if entry.files_modified %}
<span class="me-2">📁 {{ entry.files_modified|length }} file(s)</span>
{% endif %}
{% if entry.retry_count > 0 %}
<span class="me-2">🔄 {{ entry.retry_count }} retries</span>
{% endif %}
{% if entry.reflection %}
<span title="Has reflection">💡</span>
{% endif %}
</div>
<span class="badge {% if entry.outcome.value == 'success' %}bg-success{% elif entry.outcome.value == 'failure' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
{{ entry.outcome.value|upper }}
</span>
</div>
{# Detail container - populated on click #}
<div id="journal-detail-{{ entry.id }}" class="mt-3"></div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5 text-muted">
<p class="mb-0">No journal entries found.</p>
<small>Self-edit attempts will appear here.</small>
</div>
{% endif %}

View File

@@ -0,0 +1,54 @@
{# Journal entry detail partial #}
<div class="card mt-3 bg-dark-subtle border-0">
<div class="card-body">
<h6 class="card-subtitle mb-3 text-muted">Attempt Details</h6>
{% if entry.approach %}
<div class="mb-3">
<small class="text-muted">Approach:</small>
<p class="mb-0">{{ entry.approach }}</p>
</div>
{% endif %}
{% if entry.files_modified %}
<div class="mb-3">
<small class="text-muted">Files Modified:</small>
<ul class="list-unstyled mb-0">
{% for file in entry.files_modified %}
<li><code class="small">{{ file }}</code></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if entry.diff %}
<div class="mb-3">
<small class="text-muted">Diff:</small>
<pre class="small bg-black p-2 rounded overflow-auto" style="max-height: 200px;"><code>{{ entry.diff[:500] }}{% if entry.diff|length > 500 %}...{% endif %}</code></pre>
</div>
{% endif %}
{% if entry.test_results %}
<div class="mb-3">
<small class="text-muted">Test Results:</small>
<pre class="small bg-black p-2 rounded overflow-auto" style="max-height: 150px;"><code>{{ entry.test_results[:500] }}{% if entry.test_results|length > 500 %}...{% endif %}</code></pre>
</div>
{% endif %}
{% if entry.failure_analysis %}
<div class="mb-3">
<small class="text-danger">Failure Analysis:</small>
<p class="mb-0 text-danger-emphasis">{{ entry.failure_analysis }}</p>
</div>
{% endif %}
{% if entry.reflection %}
<div class="mb-0">
<small class="text-info">Reflection:</small>
<div class="p-2 bg-info-subtle rounded">
{{ entry.reflection|markdown }}
</div>
</div>
{% endif %}
</div>
</div>

View File

@@ -0,0 +1,71 @@
{# Stats cards partial for self-coding dashboard #}
<div class="row g-3">
<!-- Total Attempts -->
<div class="col-md-3 col-6">
<div class="card border-0 shadow-sm stat-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="text-muted small mb-1">Total Attempts</h6>
<h3 class="mb-0">{{ metrics.total }}</h3>
</div>
<span class="fs-4">📝</span>
</div>
</div>
</div>
</div>
<!-- Success Rate -->
<div class="col-md-3 col-6">
<div class="card border-0 shadow-sm stat-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="text-muted small mb-1">Success Rate</h6>
<h3 class="mb-0 {% if metrics.overall >= 0.7 %}text-success{% elif metrics.overall >= 0.4 %}text-warning{% else %}text-danger{% endif %}">
{{ "%.0f"|format(metrics.overall * 100) }}%
</h3>
</div>
<span class="fs-4">📊</span>
</div>
<div class="progress mt-2" style="height: 4px;">
<div class="progress-bar {% if metrics.overall >= 0.7 %}bg-success{% elif metrics.overall >= 0.4 %}bg-warning{% else %}bg-danger{% endif %}"
style="width: {{ metrics.overall * 100 }}%"></div>
</div>
</div>
</div>
</div>
<!-- Successes -->
<div class="col-md-3 col-6">
<div class="card border-0 shadow-sm stat-card h-100 border-start border-3 border-success">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="text-muted small mb-1">Successes</h6>
<h3 class="mb-0 text-success">{{ metrics.success }}</h3>
</div>
<span class="fs-4"></span>
</div>
</div>
</div>
</div>
<!-- Failures -->
<div class="col-md-3 col-6">
<div class="card border-0 shadow-sm stat-card h-100 border-start border-3 border-danger">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="text-muted small mb-1">Failures</h6>
<h3 class="mb-0 text-danger">{{ metrics.failure + metrics.rollback }}</h3>
</div>
<span class="fs-4"></span>
</div>
<small class="text-muted">
{{ metrics.failure }} fail / {{ metrics.rollback }} rollback
</small>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,184 @@
{% extends "base.html" %}
{% block title %}Self-Coding — Timmy Time{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-0">Self-Coding</h1>
<p class="text-muted small mb-0">Timmy's ability to modify its own source code</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-info" hx-get="/self-coding/stats" hx-target="#stats-container" hx-indicator="#stats-loading">
Refresh Stats
</button>
<button class="btn btn-sm btn-primary" hx-get="/self-coding/execute-form" hx-target="#execute-modal-content" onclick="document.getElementById('execute-modal').showModal()">
+ New Task
</button>
</div>
</div>
<!-- Stats Cards -->
<div id="stats-container" hx-get="/self-coding/stats" hx-trigger="load">
<div id="stats-loading" class="htmx-indicator">
<div class="d-flex justify-content-center py-4">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading stats...</span>
</div>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div class="row g-4 mt-2">
<!-- Left Column: Journal -->
<div class="col-lg-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent border-secondary d-flex justify-content-between align-items-center">
<h5 class="mb-0">Modification Journal</h5>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-secondary active" hx-get="/self-coding/journal" hx-target="#journal-container">All</button>
<button class="btn btn-outline-secondary" hx-get="/self-coding/journal?outcome=success" hx-target="#journal-container">Success</button>
<button class="btn btn-outline-secondary" hx-get="/self-coding/journal?outcome=failure" hx-target="#journal-container">Failed</button>
</div>
</div>
<div class="card-body p-0">
<div id="journal-container" hx-get="/self-coding/journal" hx-trigger="load" class="journal-list">
<div class="d-flex justify-content-center py-5">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading journal...</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Right Column: Quick Actions & Info -->
<div class="col-lg-4">
<!-- Quick Actions -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-transparent border-secondary">
<h5 class="mb-0">Quick Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-outline-info" hx-post="/self-coding/api/codebase/reindex" hx-swap="none" hx-confirm="Reindex codebase? This may take a moment.">
🔄 Reindex Codebase
</button>
<a href="/self-coding/api/codebase/summary" target="_blank" class="btn btn-outline-secondary">
📄 View Codebase Summary
</a>
</div>
</div>
</div>
<!-- Safety Info -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-transparent border-secondary">
<h5 class="mb-0">Safety Constraints</h5>
</div>
<div class="card-body">
<ul class="list-unstyled small mb-0">
<li class="mb-2">✓ Max 3 files per commit</li>
<li class="mb-2">✓ Max 100 lines changed</li>
<li class="mb-2">✓ Only files with test coverage</li>
<li class="mb-2">✓ Max 3 retries on failure</li>
<li class="mb-2">✓ Protected files cannot be modified</li>
<li>✓ All changes on feature branches</li>
</ul>
</div>
</div>
<!-- How It Works -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-transparent border-secondary">
<h5 class="mb-0">How It Works</h5>
</div>
<div class="card-body">
<ol class="small mb-0">
<li class="mb-2">Receive task description</li>
<li class="mb-2">Find relevant files via indexer</li>
<li class="mb-2">Check journal for similar attempts</li>
<li class="mb-2">Create feature branch</li>
<li class="mb-2">Plan edit with LLM</li>
<li class="mb-2">Execute via Aider or direct edit</li>
<li class="mb-2">Run tests</li>
<li class="mb-2">Commit on success, rollback on failure</li>
<li>Log attempt and reflect</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<!-- Execute Modal -->
<dialog id="execute-modal" class="rounded border-0 shadow-lg" style="max-width: 600px; width: 90%; background: var(--bs-body-bg);">
<div class="p-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Execute Self-Edit Task</h5>
<button type="button" class="btn-close" onclick="document.getElementById('execute-modal').close()"></button>
</div>
<div id="execute-modal-content">
<!-- Form loaded via HTMX -->
</div>
</div>
</dialog>
<style>
.journal-list {
max-height: 600px;
overflow-y: auto;
}
.journal-entry {
border-left: 3px solid transparent;
transition: all 0.2s ease;
}
.journal-entry:hover {
background-color: rgba(255, 255, 255, 0.03);
}
.journal-entry.success {
border-left-color: #198754;
}
.journal-entry.failure {
border-left-color: #dc3545;
}
.journal-entry.rollback {
border-left-color: #fd7e14;
}
.stat-card {
transition: transform 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
}
/* Custom scrollbar for journal */
.journal-list::-webkit-scrollbar {
width: 6px;
}
.journal-list::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
}
.journal-list::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
.journal-list::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.3);
}
</style>
{% endblock %}