Skip to content

Steward backports: htmx web frontend, prod disk and backup hygiene, finance fixes - #1072

Merged
lbedner merged 1 commit into
mainfrom
steward-backports-2
Sep 10, 2026
Merged

lbedner merged 1 commit into
mainfrom
steward-backports-2

Conversation

@lbedner

@lbedner lbedner commented Sep 8, 2026

Copy link
Copy Markdown
Owner

Closes #1070
Closes #1071

Six changes found while building aegis-steward, applied here the same day.

Themes are config (#1070)

  • Colors are --aegis-* tokens defined per [data-theme] in static/input.css; tailwind.config.js maps the aegis-* 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.
  • Two themes ship: aegis (dark, unchanged look) and aegis-light, with matching DaisyUI blocks.
  • static/js/theme.js runs synchronously in <head> before the stylesheet, applying the stored choice with no flash, and exposes toggleTheme(). New components/macros/layout.html carries theme_toggle(), placed in the base and landing navbars.
  • Every theme-blind class in the shipped templates (text-white x46, red-500, bg-black) became its token; the auth layout's grid SVG uses currentColor. app.css reads the accent from the token; color-scheme moved into the theme blocks.
  • Tests: framework tests hold the two theme blocks to parity and fail on any hex literal or theme-blind class in templates or static JS; the generated project gets tests/web/test_theme.py doing the same plus the head-order and toggle checks. styling.md rewritten around tokens.

Macro kit split by concern, plus the table, layout and chart macros

  • components/macros/{form,layout,table,feedback}.html replace the single macros.html: one file per concern, one macro per control. Auth pages import from the new homes.
  • New: 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 by base.html, opened by any swap into #dialog-body), chart_panel with static/js/charts.js (Chart.js loaded on first use, palette from the theme tokens, remounts after htmx settle and on theme change).
  • Generated tests tests/web/test_table.py and test_layout_macros.py; the framework test pins the kit's shape; docs table rewritten.

Webserver dev reload scoped to the app package (#1071)

reload_dirs is now the app package, matching the scheduler's and worker's watchers, so a host uv sync no 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 constant None, which current ty reports as redundant-condition, failing typecheck on every fresh finance project. Rendered per engine now.

Generated projects pin ty

It floated while ruff was pinned; a ty release today added the diagnostic above and broke fresh make check with 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).
  • Generated from this branch and make check end 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, status cells (badge), signed and toned money; table_row(columns, row, id, oob, actions) for the single-row answer to an action.
  • badge, dropdown + menu_item (items close their menu), confirm, oob macros; native field, text_input, money_input, textarea, select, date_input, checkbox, search_input form controls.
  • rendering.trigger(header=), close_dialog() on HX-Trigger-After-Settle (a plain trigger closes the dialog and the following swap re-opens it empty), navigate() (HX-Location). app.js listens for dialog:close.
  • Chart marker series ({"points": true}).
  • Test kit: triggers(), oob(), table_rows(), card(), stat(), chart_data(); macro tests for all of the above.
  • Finance: 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 remaining date.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() and cents_to_input() filters, the latter registered for templates.
  • goal_response() / envelope_response() public in the goals and envelopes API.
  • Tests for each; docs table and changelog updated. Probe re-rendered: ruff, ty, web + API selections green.

Added: the dashboard shape, and a fix the htmx component needed

  • page_header, figures, stats_strip, ranked_rows, underline tab_bar/tab_item, chip, range_chips, action. card and chart_panel take no title where the page already names them.
  • web_frontend/ranges.py: one set of window chips (1d through All) with since() for the pages that read backwards and horizon(days, cap) for the endpoints that take a day count.
  • The one modal in one place: hx_dialog() / hx_dialog_post() globals and dialog() / dialog_done() on the server, replacing the per-section copies.
  • Test kit: dom.location(), plus tests for every macro and helper above.
  • Fix: filters.py imported app.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 htmx project, which the earlier rounds of this PR never did.

Verified on two rendered probes: --components htmx alone (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.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. 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.

Copilot AI lite review requested due to automatic review settings September 8, 2026 00:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@lbedner
lbedner force-pushed the steward-backports-2 branch from 67c1374 to f7bd7d4 Compare September 8, 2026 00:43
@lbedner lbedner changed the title Scope the generated webserver's dev reload to the app package htmx web frontend: themes are config; webserver reload scoped to app/ Sep 8, 2026
@lbedner lbedner changed the title htmx web frontend: themes are config; webserver reload scoped to app/ htmx web frontend: themes as config, macro kit split, webserver reload scoped Sep 8, 2026
@lbedner
lbedner force-pushed the steward-backports-2 branch from f0bf188 to d177349 Compare September 8, 2026 00:59
@lbedner lbedner changed the title htmx web frontend: themes as config, macro kit split, webserver reload scoped htmx web frontend: themes, macro kit, reload scope; finance one-clock and schema-prefix fixes; pin ty Sep 8, 2026
@lbedner
lbedner force-pushed the steward-backports-2 branch 7 times, most recently from 32f8363 to 002c9a0 Compare September 9, 2026 18:04
@lbedner lbedner changed the title htmx web frontend: themes, macro kit, reload scope; finance one-clock and schema-prefix fixes; pin ty Steward backports: htmx web frontend, prod disk and backup hygiene, finance fixes Sep 9, 2026
@lbedner

lbedner commented Sep 9, 2026

Copy link
Copy Markdown
Owner Author

Added the prod disk and backup backport (aegis-pulse#186 / #187, tracked here as #1082) and re-squashed to one commit.

Disk: .dockerignore excludes backups/ (the deploy snapshot dir is the build context on the host, so COPY . /code baked every retained snapshot into the image), and aegis deploy now prunes dangling images plus build cache older than a week after the health check, and refuses to start below 10 GB free. On aegis-pulse this had reached 307 images and 851 cache records on a 77 GB droplet; the deploy died inside apt-get.

Backups: the job wrote to a relative path that resolved to the scheduler's writable layer (7.4 GB, discarded on every deploy). Now DATABASE_BACKUP_DIR with a named backup-data volume, and pg_dump -Fc restored via pg_restore --clean --if-exists. Pre-existing .sql dumps still count for retention and restore through psql. Round trip verified against a real database.

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 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 couldn't import.

Verified live on aegis-pulse: disk 100% → 14%, image 3.65 GB → 1.72 GB, scheduler container layer 7.4 GB → 18 MB.

@lbedner
lbedner force-pushed the steward-backports-2 branch 4 times, most recently from 6ad931b to 2dfb4d0 Compare September 10, 2026 01:26
…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.
@lbedner
lbedner force-pushed the steward-backports-2 branch from 2dfb4d0 to 6295461 Compare September 10, 2026 02:07
@lbedner
lbedner merged commit 2c952c2 into main Sep 10, 2026
32 checks passed
@lbedner
lbedner deleted the steward-backports-2 branch September 10, 2026 02:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants