Initial scaffold: single-month budget MVP #2

Merged
claude-code merged 6 commits from feat/1-scaffold into main 2026-04-17 11:31:12 -06:00
4 changed files with 153 additions and 132 deletions
Showing only changes of commit c6cb037f4f - Show all commits

View file

@ -1,11 +1,11 @@
:root {
--bg: #f7f6f2;
--card: #ffffff;
--ink: #1f1f1f;
--muted: #6b6b6b;
--accent: #2f6b4f;
--danger: #a03030;
--border: #d8d6cf;
--rule: #d8d6cf;
--row: #fafaf7;
}
* { box-sizing: border-box; }
@ -19,48 +19,49 @@ body {
}
header {
padding: 1.5rem 2rem 0.5rem;
padding: 1.25rem 2rem 0.25rem;
border-bottom: 1px solid var(--rule);
background: #fff;
}
header h1 {
margin: 0;
font-size: 1.6rem;
font-size: 1.4rem;
}
.subtitle {
margin: 0;
color: var(--muted);
font-size: 0.9rem;
}
main {
padding: 1rem 2rem 2rem;
padding: 1rem 0 3rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem;
.budget {
max-width: 720px;
margin: 0 auto;
padding: 0 1rem;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
.section {
margin-top: 1.5rem;
}
.card-header {
.section-header {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 0.25rem 0.5rem;
border-bottom: 2px solid var(--ink);
}
.card-header h2 {
.section-header h2 {
margin: 0;
font-size: 1.1rem;
font-size: 1.05rem;
font-weight: 600;
letter-spacing: 0.01em;
}
.total {
@ -69,92 +70,89 @@ main {
color: var(--accent);
}
ul.entries {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0.25rem;
.total.empty {
color: var(--muted);
font-weight: 500;
}
.entry {
display: grid;
grid-template-columns: 1fr auto auto;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0.5rem;
border-radius: 4px;
background: #fafaf7;
}
.entry-amount {
table.entries {
width: 100%;
border-collapse: collapse;
font-variant-numeric: tabular-nums;
}
table.entries td {
padding: 0.35rem 0.5rem;
border-bottom: 1px solid var(--rule);
vertical-align: middle;
}
tr.entry td.entry-name { width: auto; }
tr.entry td.entry-amount {
width: 9rem;
text-align: right;
color: var(--muted);
}
tr.entry td.entry-actions {
width: 2.25rem;
text-align: right;
}
.empty {
tr.empty td {
color: var(--muted);
font-style: italic;
}
tr.add-row td {
padding: 0.35rem 0.5rem;
background: transparent;
border-bottom: none;
}
.muted { color: var(--muted); font-style: italic; }
button.delete {
background: transparent;
border: none;
color: var(--danger);
font-size: 1rem;
cursor: pointer;
padding: 0 0.35rem;
padding: 0 0.25rem;
line-height: 1;
}
.add-form,
.target-form {
.add-form {
display: grid;
grid-template-columns: 1fr 7rem auto;
grid-template-columns: 1fr 9rem auto;
gap: 0.5rem;
}
.target-form {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
}
.target-form label {
grid-column: 1 / -1;
font-size: 0.85rem;
color: var(--muted);
}
.target-current {
margin: 0;
padding: 0.5rem 0.75rem;
background: #f0ece0;
border-radius: 4px;
display: flex;
justify-content: space-between;
}
.target-current.empty {
color: var(--muted);
font-style: italic;
gap: 0.5rem;
}
input[type=text],
input[type=number],
select {
padding: 0.35rem 0.5rem;
border: 1px solid var(--border);
border-radius: 4px;
border: 1px solid var(--rule);
border-radius: 3px;
font: inherit;
background: #fff;
}
button[type=submit] {
padding: 0.35rem 0.75rem;
padding: 0.35rem 0.9rem;
background: var(--accent);
color: #fff;
border: none;
border-radius: 4px;
border-radius: 3px;
cursor: pointer;
font: inherit;
}
.target-section .section-header {
border-bottom-style: dashed;
}

View file

@ -1,9 +1,13 @@
{% extends "base.html" %}
{% block content %}
<div class="grid">
<div class="budget">
{% for section in sections %}
{% include "partials/section.html" %}
{% if section.section.value == 'debt_minimum' %}
{% include "partials/section.html" %}
{% include "partials/target_card.html" %}
{% else %}
{% include "partials/section.html" %}
{% endif %}
{% endfor %}
{% include "partials/target_card.html" %}
</div>
{% endblock %}

View file

@ -1,37 +1,45 @@
<section class="card" id="section-{{ section.section.value }}">
<header class="card-header">
<section class="section" id="section-{{ section.section.value }}">
<div class="section-header">
<h2>{{ section.label }}</h2>
<span class="total" data-testid="total-{{ section.section.value }}">
${{ '%.2f' | format(section.total) }}
</span>
</header>
<ul class="entries">
{% for entry in section.entries %}
<li class="entry">
<span class="entry-name">{{ entry.name }}</span>
<span class="entry-amount">${{ '%.2f' | format(entry.amount) }}</span>
<button
class="delete"
type="button"
hx-delete="/entries/{{ entry.id }}"
hx-target="#section-{{ section.section.value }}"
hx-swap="outerHTML"
aria-label="Delete {{ entry.name }}"
>&times;</button>
</li>
{% else %}
<li class="empty">No entries yet.</li>
{% endfor %}
</ul>
<form
class="add-form"
hx-post="/sections/{{ section.section.value }}/entries"
hx-target="#section-{{ section.section.value }}"
hx-swap="outerHTML"
hx-on::after-request="if(event.detail.successful) this.reset()"
>
<input type="text" name="name" placeholder="Name" required>
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required>
<button type="submit">Add</button>
</form>
</div>
<table class="entries">
<tbody>
{% for entry in section.entries %}
<tr class="entry">
<td class="entry-name">{{ entry.name }}</td>
<td class="entry-amount">${{ '%.2f' | format(entry.amount) }}</td>
<td class="entry-actions">
<button
class="delete"
type="button"
hx-delete="/entries/{{ entry.id }}"
hx-target="#section-{{ section.section.value }}"
hx-swap="outerHTML"
aria-label="Delete {{ entry.name }}"
>&times;</button>
</td>
</tr>
{% else %}
<tr class="empty"><td colspan="3">No entries yet.</td></tr>
{% endfor %}
<tr class="add-row">
<td colspan="3">
<form
class="add-form"
hx-post="/sections/{{ section.section.value }}/entries"
hx-target="#section-{{ section.section.value }}"
hx-swap="outerHTML"
hx-on::after-request="if(event.detail.successful) this.reset()"
>
<input type="text" name="name" placeholder="Name" required>
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required>
<button type="submit">Add</button>
</form>
</td>
</tr>
</tbody>
</table>
</section>

View file

@ -1,31 +1,42 @@
<section class="card target-card" id="section-debt_target" hx-swap-oob="outerHTML">
<header class="card-header">
<section class="section target-section" id="section-debt_target" hx-swap-oob="outerHTML">
<div class="section-header">
<h2>Primary Debt Target</h2>
</header>
{% if target.entry %}
<p class="target-current">
<span class="entry-name">{{ target.entry.name }}</span>
<span class="entry-amount">${{ '%.2f' | format(target.entry.amount) }}</span>
</p>
{% else %}
<p class="target-current empty">No target selected.</p>
{% endif %}
<form
class="target-form"
hx-post="/debt-target"
hx-target="#section-debt_target"
hx-swap="outerHTML"
>
<label for="debt-target-select">Choose from Debt Minimums</label>
<select id="debt-target-select" name="debt_minimum_id">
<option value="">(none)</option>
{% for dm in debt_minimums %}
<option
value="{{ dm.id }}"
{% if target.debt_minimum_id == dm.id %}selected{% endif %}
>{{ dm.name }}: ${{ '%.2f' | format(dm.amount) }}</option>
{% endfor %}
</select>
<button type="submit">Set</button>
</form>
{% if target.entry %}
<span class="total">${{ '%.2f' | format(target.entry.amount) }}</span>
{% else %}
<span class="total empty">$0.00</span>
{% endif %}
</div>
<table class="entries">
<tbody>
<tr class="entry">
<td class="entry-name">
{% if target.entry %}{{ target.entry.name }}{% else %}<span class="muted">No target selected.</span>{% endif %}
</td>
<td class="entry-amount"></td>
<td class="entry-actions"></td>
</tr>
<tr class="add-row">
<td colspan="3">
<form
class="target-form"
hx-post="/debt-target"
hx-target="#section-debt_target"
hx-swap="outerHTML"
>
<select name="debt_minimum_id">
<option value="">(none)</option>
{% for dm in debt_minimums %}
<option
value="{{ dm.id }}"
{% if target.debt_minimum_id == dm.id %}selected{% endif %}
>{{ dm.name }}: ${{ '%.2f' | format(dm.amount) }}</option>
{% endfor %}
</select>
<button type="submit">Set</button>
</form>
</td>
</tr>
</tbody>
</table>
</section>