Steward backports: htmx web frontend, prod disk and backup hygiene, finance fixes - #1072
Conversation
67c1374 to
f7bd7d4
Compare
f0bf188 to
d177349
Compare
32f8363 to
002c9a0
Compare
|
Added the prod disk and backup backport (aegis-pulse#186 / #187, tracked here as #1082) and re-squashed to one commit. Disk: Backups: the job wrote to a relative path that resolved to the scheduler's writable layer (7.4 GB, discarded on every deploy). Now Also fixed the three tests that were red on this branch: the macro-kit split expected a stale macro list from before the row-action work; the tailwind service carried a YAML comment where generated compose services carry none (now a Jinja comment the template keeps and projects don't inherit); and Verified live on aegis-pulse: disk 100% → 14%, image 3.65 GB → 1.72 GB, scheduler container layer 7.4 GB → 18 MB. |
6ad931b to
2dfb4d0
Compare
…inance fixes Web frontend: themes as config (aegis/aegis-light tokens), the macro kit split by concern and grown for row actions and dialogs (data_table row ids, actions block, status cells, signed money; badge, dropdown, menu_item, confirm, oob; native form controls), reload scoped to the app package, an hx_replace() template global for self-replacing requests, htmx attribute settling off, close_dialog() sent after settle so a swap cannot re-open an empty dialog, navigate() (HX-Location), chart marker series, and a test kit with triggers/oob/table_rows/card/stat/chart_data. Finance: the service reads one calendar clock (the ratchet now covers the API and web routes), models render the FK schema prefix per engine, stream hydration is one function shared with single-row re-renders, and the frequency vocabulary lives in the service constants. Generated projects pin ty. Prod disk and backups (aegis-pulse#186/#187, filed as #1082): a generated project running the scheduler would fill its production host and keep no restorable backup while doing it. - .dockerignore excludes backups/. `aegis deploy` writes rollback snapshots into the deploy directory, which IS the build context on the host, so COPY . /code baked every retained snapshot into the image. On aegis-pulse that was 2.52 GB of a 3.65 GB image, rebuilt every deploy because the copied bytes change each time. - `aegis deploy` prunes dangling images and build cache older than a week once the health check passes, and refuses to start when the host is below 10 GB free. Nothing reclaimed either before: six weeks reached 307 images and 851 cache records on a 77 GB droplet, and the next deploy died inside the Dockerfile's apt-get with the files already synced. `--filter until` rather than --reserved-space, which needs Docker 28+. - The backup job wrote to a relative path, which inside the scheduler resolved to the container's writable layer: 7.4 GB, discarded on every container recreation, i.e. every deploy. The directory is now DATABASE_BACKUP_DIR and the scheduler mounts a named backup-data volume at it, so dumps survive recreation and stay out of the build context. - pg_dump writes the custom format (-Fc), compressed, restored with `pg_restore --clean --if-exists`; plain .sql dumps from before the switch are still matched for retention and restored with psql. The round trip was verified against a real database, not assumed. Three tests that were failing on this branch now pass. The macro-kit split expected a stale macro list from before the row-action work. The tailwind service carried a YAML comment where generated compose services carry none, so it became a Jinja comment the template keeps and projects do not inherit. test_finance_clock.py imported a finance-owned module without being in the finance manifest, so a stack generated without finance shipped a test that could not import. ## The chat transcript's words have one home `app/core/chat_transcript.py` holds the fence balancer, the tool-call trail labels, the attachment gate and marker, and the footer line; the Flet chat controls' `stream.py` re-exports them and keeps only the accumulator. Core rather than the AI service because those controls ship on every stack (tree hygiene), the way `app/core/formatting.py` already serves the AI CLI and the dashboard. The SSE `tool` frame now carries `label`, computed once by `tool_label`, so a browser client shows the same trail line live that the stored trace renders later instead of keeping its own copy of the rule. CLAUDE.md gains the rule behind both: duplication is a bug, fixed in the same session, because an agent changing a core thing cannot miss a second copy that does not exist.
2dfb4d0 to
6295461
Compare
Closes #1070
Closes #1071
Six changes found while building aegis-steward, applied here the same day.
Themes are config (#1070)
--aegis-*tokens defined per[data-theme]instatic/input.css;tailwind.config.jsmaps theaegis-*class names onto them (rgb(var(--aegis-bg) / <alpha-value>)), so every existing utility follows the active theme and a rebrand is a value edit.aegis(dark, unchanged look) andaegis-light, with matching DaisyUI blocks.static/js/theme.jsruns synchronously in<head>before the stylesheet, applying the stored choice with no flash, and exposestoggleTheme(). Newcomponents/macros/layout.htmlcarriestheme_toggle(), placed in the base and landing navbars.text-whitex46,red-500,bg-black) became its token; the auth layout's grid SVG usescurrentColor.app.cssreads the accent from the token;color-schememoved into the theme blocks.tests/web/test_theme.pydoing the same plus the head-order and toggle checks.styling.mdrewritten around tokens.Macro kit split by concern, plus the table, layout and chart macros
components/macros/{form,layout,table,feedback}.htmlreplace the singlemacros.html: one file per concern, one macro per control. Auth pages import from the new homes.data_table(columns, rows, empty)(declared columns, objects or dicts, formatting through the filters; the one table),card,stat_tile,dialog(native<dialog>, mounted once bybase.html, opened by any swap into#dialog-body),chart_panelwithstatic/js/charts.js(Chart.js loaded on first use, palette from the theme tokens, remounts after htmx settle and on theme change).tests/web/test_table.pyandtest_layout_macros.py; the framework test pins the kit's shape; docs table rewritten.Webserver dev reload scoped to the app package (#1071)
reload_dirsis now the app package, matching the scheduler's and worker's watchers, so a hostuv syncno longer restarts the dev webserver in a storm. Generated test pins the kwarg.Finance service reads one calendar clock
Rules, budgets, streams and the analyst defaulted to
date.today()(host-local) while models, imports and the demo seed were on UTC. Each evening in the Americas the two dates differ and the generated project's demo-dataset insight test failed with a phantom missed bill.utils.current_date()(UTC) is the only source now; a generated ratchet test keeps the local clock out of the service.Finance models render the FK schema prefix per engine
_FK = f"{_SCHEMA}." if _SCHEMA else ""on a SQLite stack is a condition on a constantNone, which current ty reports asredundant-condition, failing typecheck on every fresh finance project. Rendered per engine now.Generated projects pin
tyIt floated while ruff was pinned; a ty release today added the diagnostic above and broke fresh
make checkwith no template change. Pinned to 0.0.79; a bump is now a reviewed commit that fixes new diagnostics alongside it.Verification
tests/core/test_htmx_template_render.py: 102 passing (5 new).make checkend to end:htmx,database[sqlite]+auth: 875 passed;database[sqlite],scheduler[sqlite]+finance: 2113 passed; base stack typechecks on ty 0.0.79. djlint/ruff/ty clean throughout.Added 2026-09-08: row actions and dialogs in the kit
data_table:row_id, a{% call(row) %}actions column,statuscells (badge),signedandtonedmoney;table_row(columns, row, id, oob, actions)for the single-row answer to an action.badge,dropdown+menu_item(items close their menu),confirm,oobmacros; nativefield,text_input,money_input,textarea,select,date_input,checkbox,search_inputform controls.rendering.trigger(header=),close_dialog()onHX-Trigger-After-Settle(a plain trigger closes the dialog and the following swap re-opens it empty),navigate()(HX-Location).app.jslistens fordialog:close.{"points": true}).triggers(),oob(),table_rows(),card(),stat(),chart_data(); macro tests for all of the above.visible_streams()/hydrate_streams()/candidate_items()in the recurring API;FREQUENCY_LABELS,BILL_FREQUENCY_OPTIONS,frequency_label()in the service constants with the Flet frontend importing them; the one-clock ratchet covers the finance API and the web frontend (guarded for projects without the htmx component), with the remainingdate.today()reads fixed.Verified on a rendered probe (
--components database,scheduler,htmx,worker,redis --services finance): ruff, ty, web + finance tests green.Added 2026-09-08 (later): Budget-phase kit
stat_tile(label, value, negative, caption, attrs)(a caption line; attributes make it a clickable cell),progress(ratio, tone).money_to_cents()andcents_to_input()filters, the latter registered for templates.goal_response()/envelope_response()public in the goals and envelopes API.Added: the dashboard shape, and a fix the htmx component needed
page_header,figures,stats_strip,ranked_rows, underlinetab_bar/tab_item,chip,range_chips,action.cardandchart_paneltake no title where the page already names them.web_frontend/ranges.py: one set of window chips (1d through All) withsince()for the pages that read backwards andhorizon(days, cap)for the endpoints that take a day count.hx_dialog()/hx_dialog_post()globals anddialog()/dialog_done()on the server, replacing the per-section copies.dom.location(), plus tests for every macro and helper above.filters.pyimportedapp.services.finance.utils, so the htmx component could not be rendered without the finance service. It reads the UTC date directly now. Caught by rendering an--components htmxproject, which the earlier rounds of this PR never did.Verified on two rendered probes:
--components htmxalone (151 web tests, ruff, ty) and the finance stack (make check, 2409 passed).The chat transcript's words have one home
app/core/chat_transcript.pyholds the fence balancer, the tool-call trail labels, the attachment gate and marker, and the footer line; the Flet chat controls'stream.pyre-exports them and keeps only the accumulator. Core rather than the AI service because those controls ship on every stack (tree hygiene), the wayapp/core/formatting.pyalready serves the AI CLI and the dashboard. The SSEtoolframe now carrieslabel, computed once bytool_label, so a browser client shows the same trail line live that the stored trace renders later instead of keeping its own copy. CLAUDE.md gains the rule behind both: duplication is a bug, fixed in the same session, because an agent changing a core thing cannot miss a second copy that does not exist. Backported from steward PR 77.