Show only the last MONTHS_TO_SHOW months (default 5) in the fee table columns
so the page fits on screen without horizontal scrolling. Reconciliation still
runs over the full month history so balances, credits, and debts are unaffected.
Set MONTHS_TO_SHOW=0 to show all months. Implemented in both Python and Go.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add second Fio account (CZ0820100000002502035405 / 2502035405/2010).
Both accounts are fetched on every sync run and combined before dedup,
so the payments sheet accumulates transactions from either account.
QR codes now default to the new account.
Go:
- config.go: hardcoded Accounts/LoadedAccount slice replaces scalar
BankAccount + FioAPIToken; Config.BankAccount renamed QRAccount;
per-account tokens via FIO_API_TOKEN_NEW / FIO_API_TOKEN_OLD
- banksync.SyncToSheets: accepts []fio.Client, loops to combine txns
- cmd/fuj/main.go: buildFioClients helper; both sync call sites updated
- html_handler + build_adults/juniors: use Config.QRAccount
- New TestSyncToSheets_MultiAccount covers cross-account dedup
Python:
- config.py: ACCOUNTS list + LOADED_ACCOUNTS (tokens from env)
- fio_utils.py: fetch_transactions_for (per-account) +
fetch_transactions_all (loops all accounts)
- sync_fio_to_sheets.py: uses fetch_transactions_all
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add TestEmbedCompleteness and TestStaticAssetsServed in
go/internal/web/assets_test.go. The completeness guard walks the
on-disk templates/ and static/ directories and asserts every file is
present in the corresponding embed.FS, catching forgotten files on
future additions. The static mux test hits /static/css/app.css and all
JS files through the same http.FileServerFS wiring used in server.go,
confirming assets are served from the embedded FS with correct
Content-Type and a 404 for unknown paths.
Standalone binary smoke test passed manually: binary copied to /tmp
(no adjacent templates/ or static/), assets served correctly.
Closes M6.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bare <a href=/qr> Pay buttons with <button data-*> elements that
open an in-page #qrModal (matching Python's showPayQR UX), driven by a
new payment-qr.js vanilla-JS IIFE module. Remove the now-dead qrHref /
qrHrefAll template helpers from render.go.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds static/js/member-detail.js: fetches /api/<page> once on page load,
caches the response, and renders a per-member detail modal on [i] row click.
Keyboard nav: Esc closes, ↑/↓ walk visible (filtered) rows. All modal CSS
was already in place from M6.1.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Extract AssemblePayments(ctx) from ServePayments in api/handler.go,
mirroring the AssembleAdults/AssembleJuniors pattern
- Add PaymentsPageData view-model wrapper in render.go
- Rewire html_handler.go ServePayments to call AssemblePayments and
render with PaymentsPageData
- Replace payments.tmpl placeholder with real grouped-by-person ledger:
alphabetical member blocks, txn-table (Date/Amount/Purpose/Message),
newest-first rows, Unmatched/Unknown bucket
- Append ledger CSS classes to app.css (.ledger-container, .member-block,
.txn-table, .txn-date/amount/purpose/message, tr:hover)
- Add TestPaymentsPage markup test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Extract AssembleJuniors(ctx) from ServeJuniors JSON handler so HTML
and JSON share the same view-model path (mirrors AssembleAdults pattern)
- Add JuniorsPageData wrapper in render.go
- Wire HTMLHandler.ServeJuniors to AssembleJuniors + render template
- Replace 4-line placeholder juniors.tmpl with full template:
member table, name filter, month-range filter, totals row,
Credits + Debts sections, Pay / Pay All buttons via /qr links
(no Unmatched section — matches Python juniors.html parity)
- J/A attendance breakdown ("3/500 CZK (4:2J,1A)") and "?" sentinel
rendered via MonthCell.Text from buildJuniorMemberRow, no extra
template logic needed
- All tests pass; make parity reports 3/3 routes OK
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Map unpaid|partial → cell-unpaid (or cell-unpaid-current for current
month) and surplus → cell-overridden, matching Python's Jinja logic;
avoids emitting non-existent cell-partial/cell-surplus classes that
caused Pay buttons to escape the table.
- Add text-decoration: none to .pay-btn so anchor-based Pay links don't
show the default underline.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The lifted CSS defines .table-container with border: 1px solid #333 and
max-width: 1200px — without the wrapper the table stretched to full
viewport width and showed no border. Mirrors templates/adults.html.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Extract AssembleAdults(ctx) from ServeAdults so HTML and JSON API share one reconcile path.
- HTMLHandler gains *api.Handler; ServeAdults loads real data and renders adults.tmpl.
- AdultsPageData view model + qrHref/qrHrefAll funcMap (URL-encode /qr params, YYYY-MM→MM/YYYY).
- adults.tmpl: full reconcile table, per-cell status classes + cell-unpaid-current, Pay button hrefs,
totals row, credits/debts/unmatched sections, filter controls, sheet links.
- static/js/filters.js: NFD-normalize name filter + month-range column hiding; future months hidden by default.
- TestAdultsPage asserts member name and cell text against fixture data.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stand up the Go-native HTML frontend foundation:
- base.tmpl layout + nav/footer partials (three-tier nav, active-link highlighting)
- terminal-green-on-black theme extracted to static/css/app.css (served via embed.FS)
- HTMLHandler with stub pages for all five routes; / redirects to /adults
- NewRenderer parses per-page template sets at startup so parse failures abort boot
- Smoke test: each route returns 200 text/html with exactly one class="active" link
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add internal/web/api package with Go structs for every /api/X route:
AdultsResponse, JuniorsResponse, PaymentsResponse, VersionResponse.
All fields carry explicit json: tags matching the Python view-model keys.
Key design choices:
- member_data / month_labels / raw_payments are nested objects (not
the pre-serialised JSON strings used in Jinja templates)
- Expected{Value int; Unknown bool} with custom MarshalJSON emits int
or the string "?" for junior single-attendance months
- RawTransaction covers the full 11-column payments sheet row
schemagen_test.go reflects all four response types via
github.com/invopop/jsonschema and golden-compares against committed
schemas in tests/fixtures/api-schema/. The JSONSchema() method on
Expected lives in the test file so the prod binary has no jsonschema
dependency.
Closes M5.1 in docs/plans/2026-05-03-2349-go-backend-rewrite-progress.md.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>