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

View file

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

View file

@ -1,15 +1,17 @@
<section class="card" id="section-{{ section.section.value }}"> <section class="section" id="section-{{ section.section.value }}">
<header class="card-header"> <div class="section-header">
<h2>{{ section.label }}</h2> <h2>{{ section.label }}</h2>
<span class="total" data-testid="total-{{ section.section.value }}"> <span class="total" data-testid="total-{{ section.section.value }}">
${{ '%.2f' | format(section.total) }} ${{ '%.2f' | format(section.total) }}
</span> </span>
</header> </div>
<ul class="entries"> <table class="entries">
<tbody>
{% for entry in section.entries %} {% for entry in section.entries %}
<li class="entry"> <tr class="entry">
<span class="entry-name">{{ entry.name }}</span> <td class="entry-name">{{ entry.name }}</td>
<span class="entry-amount">${{ '%.2f' | format(entry.amount) }}</span> <td class="entry-amount">${{ '%.2f' | format(entry.amount) }}</td>
<td class="entry-actions">
<button <button
class="delete" class="delete"
type="button" type="button"
@ -18,11 +20,13 @@
hx-swap="outerHTML" hx-swap="outerHTML"
aria-label="Delete {{ entry.name }}" aria-label="Delete {{ entry.name }}"
>&times;</button> >&times;</button>
</li> </td>
</tr>
{% else %} {% else %}
<li class="empty">No entries yet.</li> <tr class="empty"><td colspan="3">No entries yet.</td></tr>
{% endfor %} {% endfor %}
</ul> <tr class="add-row">
<td colspan="3">
<form <form
class="add-form" class="add-form"
hx-post="/sections/{{ section.section.value }}/entries" hx-post="/sections/{{ section.section.value }}/entries"
@ -34,4 +38,8 @@
<input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required> <input type="number" name="amount" step="0.01" min="0" placeholder="0.00" required>
<button type="submit">Add</button> <button type="submit">Add</button>
</form> </form>
</td>
</tr>
</tbody>
</table>
</section> </section>

View file

@ -1,23 +1,30 @@
<section class="card target-card" id="section-debt_target" hx-swap-oob="outerHTML"> <section class="section target-section" id="section-debt_target" hx-swap-oob="outerHTML">
<header class="card-header"> <div class="section-header">
<h2>Primary Debt Target</h2> <h2>Primary Debt Target</h2>
</header>
{% if target.entry %} {% if target.entry %}
<p class="target-current"> <span class="total">${{ '%.2f' | format(target.entry.amount) }}</span>
<span class="entry-name">{{ target.entry.name }}</span>
<span class="entry-amount">${{ '%.2f' | format(target.entry.amount) }}</span>
</p>
{% else %} {% else %}
<p class="target-current empty">No target selected.</p> <span class="total empty">$0.00</span>
{% endif %} {% 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 <form
class="target-form" class="target-form"
hx-post="/debt-target" hx-post="/debt-target"
hx-target="#section-debt_target" hx-target="#section-debt_target"
hx-swap="outerHTML" hx-swap="outerHTML"
> >
<label for="debt-target-select">Choose from Debt Minimums</label> <select name="debt_minimum_id">
<select id="debt-target-select" name="debt_minimum_id">
<option value="">(none)</option> <option value="">(none)</option>
{% for dm in debt_minimums %} {% for dm in debt_minimums %}
<option <option
@ -28,4 +35,8 @@
</select> </select>
<button type="submit">Set</button> <button type="submit">Set</button>
</form> </form>
</td>
</tr>
</tbody>
</table>
</section> </section>