v13.15.1
Two release-gate workflows behind this cut. Patch class — no API
changes, no schema deltas.
Phase DB.3 followups (operator-driven 2026-06-12)
- App2 Sankey tooltip-only labels (operator: "the combining
issue"). Drop the always-visible inline<text>labels that
stacked into illegible overlap at 50+ nodes (L2FT Multi-Leg
Flow). Mirror QS's hover-only behavior with SVG<title>
children on every node + link. Native browser tooltips; no JS
overlay. Currency formatting viaIntl.NumberFormatUSD. - QS BarChart count() axis label fix.
_axis_label_apply_to
now mirrors BL.1'scount() → _row_one_*field-well rewrite so
ApplyTo.Column.ColumnNamematches the well. QS was silently
dropping thevalue_labeloverride on mismatch (Pending Aging
and L2 Exceptions x-axis read_row_one_l1_*_ds (Sum)instead
of the configured label). - QS embed tall-viewport for the cold-read parity capture
pass (1600, 1000) → (1600, 4000). QS uses internal scroll
containers sopage.screenshot(full_page=True)only captured
the viewport region; the tall viewport lets sheet content
(KPI + chart + table + below-fold prose) render in one frame. - L2FT Transfer Templates sheet target for the parity-verify
Sankey test — the multi-leg Sankey lives on Transfer Templates,
not Chains. Test slug + audit-doc cross-references updated.
Phase DG — CI database hygiene + triage (full cycle)
Five-leaf phase that root-caused the multi-week CI red streak +
shipped the four supporting fixes.
- DG.0 audit (
docs/audits/dg_0_db_hygiene_audit.md). The
red streak was POSIX/dev/shmsaturation (per-container 64MB
tmpfs default) under xdist-n 4concurrent matview refreshes,
plus accumulated per-test schema debris from
tests/e2e/_isolation.py:158's silent-swallow best-effort
teardown that fed on persistent CI containers across runs. - DG.1 fail-loud teardown. Drop the swallow; collect failures
into_TEARDOWN_FAILURES+ surface at session-end via
pytest_sessionfinishwith a clearly-marked summary block.
Raisessession.exitstatusto non-zero when non-empty so CI
can't ignore future teardown failures. - DG.2 container-boot scorched-earth sweep. New runner step
before the test layers fire — discovers every<base>_<6hex>_*
object viapg_matviews/pg_views/pg_indexes/
pg_tables(PG) or the equivalentuser_*catalogs (Oracle)
and drops withCASCADEin dependency-safe order. Idempotent- cleans cross-run debris in O(N) per kind. DuckDB no-op.
- DG.3 ci-shared-pg
--shm-size=2gon container creation +
adoption-side check that recreates a reused container with the
64MB default. The actual root cause of the DiskFull cascade —
DG.2 cleans accumulated debris but doesn't reduce per-run
/dev/shmpressure from concurrent activity. - DG.3 always-on hang diagnostic via stdlib faulthandler.
Per[tool.pytest.ini_options] faulthandler_timeout = 180.
Any test running >180s dumps thread tracebacks to stderr +
continues (faulthandler reports, doesn't kill — full fixture
teardown preserved). The runner's per-layer post-step greps
stderr.log for trip count + emits
[heartbeat-hit] layer=<X> — N trip(s)so CI logs surface
it. Stack identifies the exact wedge location (Playwright's
run_forever, psycopg socket blocks, matview refresh stalls).
Always on; no opt-in flag. - DG.3 test-side picker fixes. The 12 v13.15.1-gate qs_browser
failures bisected as 8× DiskFull cascade (cleared by shm-size)- 2× test-harness gaps + 2× pre-existing real bugs. Fix shapes:
_assert_pickableswitchesfilter_options→
typeahead_filter(virtualized MUI Autocomplete dropdowns
only mount ~12 alphabetical options on open; late-alphabet
accounts likeZBASubAccount/WireSettlementSuspense
silently fell out of the membership check).wait_for_dropdown_options_presentcatches innerTimeoutError- retries so the outer 15s budget actually does what it says
(was a single 2s inner failure propagating).
- retries so the outer 15s budget actually does what it says
_open_control_dropdowndoes attached→scroll_into_view→
visible instead of single state="visible" wait. Sheets with
many control bar entries (Transactions has 5+) push later
controls into a second row that's below the initial viewport.- 1s→5s search-input probe + skip the option-wait when
search-variant was nudged. Typeahead pickers with empty seed
don't surface options until something is typed; that's the
caller's job. - Explicit Search-button click in
narrow_dropdown_options_by_query+set_dropdown_value.
QS's high-cardinality typeahead pickers (Transactions
DS_L1_TX_IDS, 8k+ rows) render an explicit "Search" button
instead of auto-narrowing on input change. Probe via
button:has-text("Search")inside the popover containers +
click if present; absent → MUI auto-narrowing variant fires
on input. TreeValidatorsettle-retry loop. Recipient Fanout's 3
distinct_count()KPIs lag the Table's mount under CI load;
single-shot diff after per-titlewait_loadedmissed them.
Re-poll up to 3× / 1.5s after the initial diff.
Out of scope (release-time triage)
One residual qs_browser failure (test_inv_drilldown.py::test_account_network_table_walk_rerenders_table[qs])
xfailed strict=False — same Search-button-class typeahead picker
shape as the Transactions-Transfer case, this one with a
different option DOM that _OPTION_SELECTOR doesn't catch yet.
Investigation lives in PLAN backlog; will surface as XPASS when
fixed.
Three operator-dogfood bug-fix themes plus a substantive new feature
(Phase DA) collected into one release. Minor bump for the new App2
decoration feature (App2's renderer now paints drillable-column visuals
in parity with the QuickSight side).
1. Phase DA — App2/QS click-drill decoration parity + type-system gate.
Operator dogfood (2026-06-12, L1 Overdraft sheet) surfaced two coupled
defects in the click-drill decoration story:
- App2's
renderTableignoredconditional_formattingentirely, so
every drillable column looked identical to non-drillable columns —
operator couldn't tell which cells carried drills until they hovered. - Every L1 app site misused
CellAccentText(the LEFT-click cue) for
DATA_POINT_MENUdrills. Zero usage ofCellAccentMenuanywhere.
Even on the QS side the column showed plain accent text while the
actual drill was menu-only — analyst expectation vs reality drift.
The audit at docs/audits/da_0_clickability_audit.md found 15 mutations
across 12 Tables in 4 apps. Phase DA landed:
- Collapsed two-type CellFormat design to one
Drillable(on=Dim, color=str)marker. The visual cue (plain accent text vs accent
text + tint background) auto-derives from the drill triggers writing
fromon.columnat QS-emit time and at App2 plan-build time. Authors
declare "this column carries a drill"; the type system + renderer
pick the visual from the drill set:- any
DATA_POINT_MENUdrill writes from the column → accent text- accent-tint background
- only
DATA_POINT_CLICKdrill(s) → accent text only
DroppedCellAccentText+CellAccentMenu+ theCellFormatunion
common/clickability.py+tests/unit/test_clickability.py—
pre-stable posture, no compat shim.
- any
- App2 renderer plumbing.
_VisualPlangrows
column_decoration: Mapping[str, str];_table_column_metawalks
visual.conditional_formattingand resolves the per-column kind via
the sameDrillable.visual_kind(drills)code path the QS-side
Drillable.emituses — App2 ≡ QS by construction.
_data_shape.shape_tableforwards ascolumn.decoration;bootstrap. js::renderTablepaintscell-accent/cell-accent-menuCSS
classes on each<td>. - CSS + cell-click affordance.
widgets-theme.cssadds
.cell-accent { color: var(--color-accent); font-weight: 500 }and
.cell-accent-menu { color: var(--color-accent); background: color-mix(in srgb, var(--color-accent) 10%, transparent); cursor: pointer }.bootstrap.js::wireRowDrillsbinds left-click on
<td.cell-accent-menu>→ opens the menu drill (same code path as the
⋯ button).stopPropagation()prevents the row-level CLICK drill
from firing on Class B mix cells. Documents an operator-locked
exception to "left clicks move LEFT" — App2 may break the rule when
the explicit visual cue makes the affordance discoverable. - Apps sweep. All 13 existing
CellAccentTextcallsites in
apps/l1_dashboard/app.pymigrated toDrillable. Class C wires:
Transactions Audittransfer_id→ Posting Ledger (uses the existing
_DP_TX_TRANSFERlanding pad +_wide_date_writes()pattern); Daily
Balances Auditdb_account_id→ Daily Statement; Posting Ledger
account_id→ Daily Statement (addedbusiness_daycolumn to
l1-transactions-dsviadate_trunc_day('posting', dialect)at
SELECT time, taggedColumnShape.DATETIME_DAYso the drill writes a
day-grain date). Class C strip: Posting Ledgertransfer_id
(self-drill). Class D adds: L2FT Violation Detailentity_a;
Investigation Account Network — Touching Edgescounterparty. - Type-system gate at
Table.__post_init__(per
[[feedback_invariants_in_types]]). Walks
conditional_formatting × actions; for eachDrillable.on.column,
asserts ≥1 drill writes from that column. RaisesValueErrorat
construction with diagnostic listing every Drill on the Table + the
columns each one writes from, so the operator can spot off-by-one
column-name typos at a glance. Permanently prevents the bug class
from recurring. - Convention origin memory. "Left clicks move LEFT, right clicks
move RIGHT" is a QuickSight-limitation workaround, not a deep design
principle (operator clarification). App2 may break the rule when a
better affordance exists — Phase DA's cell-click-opens-menu is one
such authorized break. - Deployed-Studio handbook 404 fix. The per-sheet
?button's
handbook source (docs/handbook/_shared/,l1/,executives/, etc.)
moved into the wheel package at
src/recon_gen/docs/_handbook_per_sheet/. The Starlette
/handbook/<path>route updated toparents[2]/docs/_handbook_per_sheet/
so it resolves identically in repo-checkout AND wheel-install. Mkdocs
configured toexclude_docs: _handbook_per_sheet/so the per-sheet
snippets stay out of the curated site build. - QS uppercase-hex fix.
_tint_hexemits{:02X}so the auto-derived
background tint satisfies QS's^#[A-F0-9]{6}$validation pattern
(CI 27439942692 caught the original lowercase-hex regression).
Full audit + locks + per-site resolution: docs/audits/da_0_clickability_audit.md.
App2-side parity screenshots: docs/audits/da_7_app2_snaps/.
Parity verify checklist: docs/audits/da_7_parity_verify.md.
2. Tom Select clear-button (× to clear a selection). Single-select
pickers previously required click-into + Delete-key to clear; the
operator flagged this as friction. Wired Tom Select's clear_button
plugin so a one-click ✕ at the right edge of the picker clears the
selection. Multi-select pickers also carry both remove_button
(per-chip ×) AND clear_button (clear-all ×) for symmetric affordance.
Visual polish iterated against operator dogfood feedback:
- Override vendor cascade so the × pins to the right edge.
- 1.5rem circular click target with danger-tint hover background.
- Vertical-centered via
top: 50% + translateY(-50%)(overrides
vendor's fixedtop: 8pxwhich assumed default 20px-tall
.ts-control). - Swapped the glyph from U+2A2F ⨯ (math operator — math-axis ink, not
geometric center) to U+2715 ✕ (canonical UI close glyph, designed by
font vendors to optically center) via.clear-button::before.
Eliminates the "× floats above text baseline" perception.
Anti-regression test in test_html_filter_widgets.py pins the plugin
shape so future refactors can't silently drop the affordance.
3. Boot-id cache-bust for page-shell static assets. The shared
page shell in render.py hardcoded bare /static/output.css,
/static/widgets-theme.css, and seven vendor JS+CSS files. Iterating
on widgets-theme.css required the operator to Cmd+Shift+R after every
Studio restart to escape the browser cache.
Lifted Studio's existing _BOOT_ID + asset_url() helper (a
process-lifetime random hex token) from _studio_routes.py up to
render.py so the shared page shell's _VENDOR_CSS + _VENDOR_JS +
_PAGE_SHELL all route through it. Every <link> / <script> URL
the shell emits now carries ?cb=<boot>. Studio restart flips the
token, forcing every browser to refetch every asset on next page load
— no hard refresh needed.
Anti-regression test in test_vendor_assets.py
(test_page_shell_static_urls_cache_busted) regexes every
(href|src)="/static/..." out of a rendered shell and asserts ?cb=
appears in each one.
4. Row-drill MENU-only contract restored. Operator dogfood found
the L1 Overdraft sheet's table was making the entire row a left-click
target despite being declared as DATA_POINT_MENU-only. Root cause:
a clickDrill = drills[0] fallback in
bootstrap.js::wireRowDrills that promoted any first drill to a
row-wide left-click handler, overriding the documented contract.
Fallback removed. The existing
test_menu_drill_adds_ellipsis_button_per_row_and_header_cell test
was wrong (asserted n_drillable == 2 for MENU-only) — updated to
assert the correct shape: MENU-only Tables have n_drillable == 0,
no cursor: pointer on <tr>, and surface their drill ONLY via the
⋯ button + ctxmenu.