MAF persists the thread at run END, so clearing mid-stream deleted the
row only for the finishing run to write the conversation right back —
the thread reappeared after the reload ("chunks" needing a second
clear). The button is now disabled while agent.isRunning.
(The chunked-clear reports on the prior build had a second cause fixed
in 0f34a64: clearing left the client's message copy alive and the next
question re-persisted it.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Clearing in place (setMessages + remount) left the conversation
rendered — CopilotKit keeps chat state beyond agent.messages — so the
button now reloads the page after deleting the thread: a fresh boot
hydrates from the empty store and shows the welcome screen with
suggestions, which previously only appeared after a manual refresh.
Supporting changes:
- CopilotChat gets an explicit agentId="wealthysmart" (without it, it
resolves DEFAULT_AGENT_ID and can bind a different client agent
instance than useAgent's).
- The BFF answers CopilotChat's POST …/connect with an
immediately-completed stream: the runtime's in-memory runner replays
this PROCESS's event history, which resurrects cleared conversations
and double-feeds hydration. The empty-messages hydration run against
the DB store is the single restore path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The CR-hardcoded today broke two scenarios: the owner traveling, and
any future non-CR user. Now the provider sends the browser's IANA
timezone (X-Client-Timezone) with every CopilotKit request — the
runtime forwards x-* headers to the agent on its own — and the backend
binds it to a per-request ContextVar next to the DB session.
get_current_date and the future-cuota bounds use it; the tool also
reports the timezone so the model can echo it. Unknown or absent zones
(n8n posts, tests) fall back to Costa Rica; comma-joined duplicate
header values are tolerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prod bug: earlier assistant replies vanished as new turns completed and
only came back on refresh. Debug capture showed why: with persistence
on, the run-end snapshot rebuilds history under STORE ids while the
client holds it under STREAMED ids (MAF re-mints post-tool-call text
ids, their #3619). ag-ui apply treats the snapshot as authoritative —
matching ids get replaced (streamed text wiped by the store's
empty-content toolCall entry) and the store's text copies were being
filtered out by ReconcileSnapshotMiddleware as presumed duplicates, so
prior answers had no surviving representation.
Reconciling id-mismatched snapshots is unwinnable; skip them instead.
During a normal run the client already built the turn from streamed
events and the snapshot adds nothing. Hydration replays (empty
input.messages) still pass through — there the snapshot IS the
conversation, and every reload converges the client to store ids.
ReconcileSnapshotMiddleware is replaced by the much simpler
DropRunMessagesSnapshotMiddleware.
Verified in dev: multi-turn history persists across runs, one card that
survives follow-ups, reload replays everything, no duplicates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After the client returns a render tool's result, the model sometimes
re-issues the identical call with a fresh id on the follow-up run,
painting a second copy of the spending-summary card (observed live on
MAF 1.10 / CK 1.62). DeduplicateToolCallMiddleware now tracks which
render tools already ran in the current user turn and drops repeats —
their streamed TOOL_CALL_* events and their entries in the run's
MESSAGES_SNAPSHOT. The system prompt also tells the model the card is
already displayed once its "ok" result is present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Asistente.tsx pins threadId "main" on CopilotChat (an explicit stable
id stops the per-mount message wipe), runs a hydration pass on page
load — an empty-messages run makes MAF replay the stored snapshot as
MESSAGES_SNAPSHOT with no LLM call — and sends the Inicio ask-box
question only after hydration so every normal run carries the full
client-known history. "Nueva conversación" clears the stored thread
behind a ConfirmDialog.
- server.ts: the BFF no longer injects context into empty-messages
requests (that would defeat MAF's hydration trigger), and the snapshot
reconciliation passes hydration replays through untouched and treats
ids the client already sent as history rather than duplicates.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Back MAF's opt-in AG-UI snapshot persistence with a DB store so the
conversation survives page reloads and restarts:
- ChatThreadSnapshot: one upserted row per (scope, thread_id), JSONB
messages/state/interrupt (migration 794baf50635b)
- PostgresSnapshotStore implements the AGUIThreadSnapshotStore protocol.
It opens its own sessions (MAF saves during SSE streaming, after the
request middleware has closed the ContextVar session) and sanitizes on
save: camelCase toolCalls/toolCallId (the @ag-ui/client Zod schema
strips snake_case, which would lose cards on replay), drop BFF ctx-*
context messages, dedupe MAF's re-recorded multi-run turns, and drop
orphaned tool results
- Endpoint mount gains snapshot_store + a constant scope resolver
(single-user app; auth already enforced by agent_auth_and_session)
- DELETE /api/v1/chat/thread resets the conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retiring it in the middleware audit was premature: MAF 1.10 reuses the
streamed id for plain-text snapshots but deliberately mints a fresh UUID
for post-tool-call assistant text (their #3619), so tool+text turns
still rendered a duplicate assistant bubble (re-verified live). The
user-message duplication that motivated the retirement only fired on
a2ui retry runs, which no longer exist now that a2ui is disabled.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CK 1.60+ validates generated a2ui ops against a component catalog before
painting, and our agent's ops fail validation — surfaces hang at
"Building interface" forever (MAF also drops RunAgentInput.context, so
the catalog/guidelines never even reach the model). Disable a2ui end to
end until the pipeline matures:
- server.ts: a2ui { enabled: false }; RENDER_TOOLS no longer includes
render_a2ui
- App.tsx: remove the a2ui provider prop
- agent.py: prompt now asks for clean markdown tables for lists and
structured data; render_spending_summary card guidance unchanged
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- ReconcileSnapshotMiddleware removed: MAF now reuses the streamed
message id in MESSAGES_SNAPSHOT (verified in _agent_run.py at
python-1.10.0 and live event logs); keeping the surgery duplicated
user messages on a2ui follow-up runs.
- beforeRequestMiddleware now injects RunAgentInput.context as a system
message: MAF declares the field but never feeds it to the model,
which starves the model of CopilotKit's A2UI catalog/guidelines.
Remove when MAF consumes context upstream.
- Workaround chain env-gated: CK_MIDDLEWARES=off runs bare for audits,
CK_DEBUG_EVENTS=1 enables the event logger.
- Kept: SuppressRenderToolText, StripModelArtifacts,
DeduplicateToolCall, pairOrphanToolCalls (behavior-verified).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
No API drift: v2 hooks, v1 useCopilotAction interop, runtime endpoint
and a2ui config all verified against the v1.62.2 source. Brings the
1.60.2 chat scroll stability fixes and 1.57.2 thread-state fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shell revamp left SidebarInset unbounded (wrapper is min-h-svh), so
Asistente's flex chain resolved to content height — the chat's internal
scroller never engaged and long answers hid behind the composer.
Asistente now gets h-svh + overflow-hidden on the inset; every other
page keeps body scroll. Verified: inset == viewport on /asistente,
body scroll intact elsewhere.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unifies the transform pipeline on Oxc now that Rolldown does the
bundling. Dev server boot 353ms -> 188ms; build parity (2.03s).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last JS-based stage of the toolchain (dev was already SWC+esbuild).
Local build: 18.75s/1.83GB peak RSS -> 1.97s/1.27GB, output verified
identical in the browser.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The UI revamp pushed rollup's peak past the 1536MB cap and the prod
image build OOM'd. Verified locally: fails at 1536, builds at 2048.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The default pointed at the compose-internal 'backend' hostname, which
never resolves when the Hono runtime runs on the host — every local
agent run died with fetch failed/ENOTFOUND before reaching FastAPI.
Prod is unaffected (sets both URLs explicitly).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
by-category and daily-spending accept start_date/end_date (range wins
over cycle); Analytics gets a two-month calendar range picker beside
the cycle selector. Fixes a latent float/Decimal TypeError that 500'd
spending_by_category whenever it had data — the category donut had
never rendered. Regression + range tests added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gasto del ciclo reveals the by-source breakdown, Próximas cuotas the
full active-plan list, Pensión last-period rendimientos per fund.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Header search trigger + global hotkey. Typing a question surfaces a
'Preguntar' item that lands in Asistente via the same auto-send flow
as the Inicio ask box. Also installs hover-card and calendar for the
remaining Phase 3 work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Financiamientos shows cuota progress as Progress bars; Sincronización
uses a single Card of Item rows; Items Recurrentes table wrapped in a
Card like every other table; both window.confirm sites replaced with
the app's ConfirmDialog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PageHeader on Budget, Proyecciones, Pensiones, Planificador,
Municipalidad, Sincronización, Financiamientos, Asistente. Pensiones
fund identity and Municipalidad meter/charge series now use the
validated chart tokens (color-mix for translucent fills). Planificador
stat cards on StatTile; deterministic chart rendering everywhere.
Municipalidad page title now matches its nav/breadcrumb label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lib/charts.ts centralizes the color system: fixed categorical slots
(never cycled, >5 folds into Otros, 'Sin categoría' reads neutral) and
color-mix ramps that stay correct in dark mode. Animations off so
charts render deterministically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Categories fold into 'Otros' past 5 slots (fixed hue order, never
cycled); daily spending excludes future-dated Tasa Cero cuotas; rate
chart gets a legend; PageHeader with the cycle selector as action.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- PageHeader + StatTile standardize the four header and three stat-tile
variants found in the audit; Inicio and Salarios migrated as proof.
- chart tokens replaced with a teal-anchored categorical scale, CVD-
ordered and validated (six checks, light + dark surfaces).
- Registry adds: empty, field, item, spinner, progress, toggle-group,
input-group, popover, kbd (+ toggle dep).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Próximas cuotas: headline is the upcoming billing batch (sum of every
active plan's next cuota, using the rounding-absorbing last cuota
when it's the pending one) — the in-cycle formula was ~always zero
since cuotas bill on the 19th, day one of the next cycle.
- useAgent must name the 'wealthysmart' agent (default crashed the page).
- /transactions/recent excludes future-dated Tasa Cero cuotas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stat cards (gasto del ciclo, próximas cuotas Tasa Cero, pensión),
ask-the-assistant box that lands in Asistente and auto-sends via
agent.addMessage + runAgent, and últimas transacciones. Index route,
login redirects updated; Asistente stays at /asistente.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AppSidebar (icon-collapsible, sync warning badge, brand header),
NavUser footer (avatar + privacy/theme/logout dropdown), and a thin
header with SidebarTrigger, breadcrumbs, and quick toggles. Mobile
drawer and cmd+B come from the sidebar primitives.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
navigation.ts becomes the single source of truth for sidebar and
breadcrumbs (Inicio entry added; '/' is exact-match).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registry-installed Base UI flavor; existing ui components untouched.
Also drops the meaningless empty registries key from components.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Toolbar (source tabs, search, CSV, add) and a summary strip (billing
cycle range, count, per-currency total) now live inside the table card
instead of five loose control rows. Cycle range 18-to-18 is finally
visible. Stray English strings translated; back-button spacing fixed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The clicked year/month now travel in the navigation state; Budget
seeds useBudget with them instead of always defaulting to today.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
FCL projection target defaults to the next 5-year withdrawal window
(CURRENT_AGE + 5) rather than a fixed 35.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Compras minus devoluciones, one figure per currency, excluding Tasa
Cero anchors (their cuotas are what count). Tooltip explains the math.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Violet 'Tasa Cero'/'Cuota' badges (anchor amount struck through like
deferred), convert/edit action on CC purchases with a live cuota
preview, and a Financiamientos page mirroring the bank's tab (progress,
monto cuota, saldo inicial/faltante, totals).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
manifest.webmanifest with the existing icons, apple-touch-icon, and SW
registration at startup. The old sw.js was a self-unregistering cleanup
stub — meaning serviceWorker.ready never resolved and web push has been
silently dead; the new worker handles push display and notification
clicks, and makes the app installable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New Planificador page: the current savings pace (prefilled from the
live budget projection's monthly net, editable) versus a scenario with
extra monthly savings, both compounded at a configurable annual rate
over a 1-40 year horizon — side-by-side totals and growth curves.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Analytics gains a 90-day USD/CRC buy/sell chart from the existing
history endpoint (wishlist: rate history). Pensiones gains a
Calculadora de Aporte: fund + target amount + target age → required
monthly contribution via the annuity future-value formula, using the
fund's live balance and configured rate (wishlist: contribution
calculator).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One chevron component now drives year/month stepping in Budget and
Proyecciones (UX-03). Jumping from Proyecciones to a Budget month shows
a back affordance (UX-22). Water meters can be named (Casa, Cochera…)
via a settings-persisted dialog; the chart legend uses the names
(UX-24).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every dict-returning route the SPA consumes now declares a response
model (auth, bulk, sync-status, notifications). api-types.gen.ts is
generated from app.openapi() via openapi-typescript, and the 22 hand-
written read interfaces in api.ts are now aliases onto the generated
schemas — backend schema drift becomes a typecheck failure, and CI
regenerates the file and fails the deploy if it's stale. The aliasing
immediately caught two real drifts: RecurringItemType was missing
SAVINGS, and raw_charges (untyped JSON column) is now explicitly
shaped at the boundary.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-fund contribution and annual-rate assumptions are now editable in a
Supuestos dialog on Pensiones and persisted server-side in UserSettings
so projections survive devices and reloads; saldo inicial keeps coming
from the latest snapshot (UX-12). The recurring-item dialog confirms
before discarding unsaved edits, with the dirty baseline computed from
the same values the populate effect sets (UX-06). Empty transaction
lists now offer an inline add button (UX-14).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transactions gain a checkbox column with select-all; a bulk bar offers
assign-category, defer/restore and delete (confirmed) via the new bulk
endpoint, with toasts and invalidation. The paste-import modal now
lists each skipped duplicate with the matched existing row and offers
'Importar duplicados de todos modos' — re-importing ONLY the skipped
lines so first-pass rows can't double-import (UX-20). Two real fixes
found en route: the mobile list ignored undo-pending deletes and used
en-US dates; the desktop date column also lost its en-US stray. Note:
UX-11 (no mobile layout) was a false positive — a card layout already
existed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fund visibility toggles and the header privacy/theme buttons expose
aria-pressed (FE-16); deferred transactions render struck-through and
muted so the state is visible at a glance (UX-05); the balance-override
input documents its keyboard contract (UX-21). The Budget CSV button
rides in the transactions tab header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After confirming a delete the row disappears immediately but the API
call waits 5 seconds behind a Deshacer toast action; undo restores the
row without any server round-trip. A failed commit restores the row
and reports the error.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/v1/transactions/export streams a CSV (optional source/type/
date filters), cookie-authenticated so window.open downloads work.
Buttons on the Budget transactions tab (all) and Salarios (SALARY
only). Quoting and category resolution covered by tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The n8n flows fail invisibly from the app's perspective (review UX-17,
the top trust gap). /api/v1/sync-status derives last-received + cadence
thresholds for BAC, salary, municipal, pensions and exchange rate from
existing data — no n8n integration needed. New Sincronización page
shows per-source freshness and a hint to check n8n; the sidebar item
gets an amber dot when any source is stale. Verified live against the
dev DB (correctly flags its stale BAC data at 43 days).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
src/lib/dates.ts is the single source for month names and date
formatting; the three per-page month arrays are gone (with an
off-by-one guard — the shared array is 1-indexed). formatDate and the
Analytics chart tooltips now speak es-CR, Analytics headings are
Spanish, and the cycle selector says 'Todo el período' (UX-19, FE-17,
UX-07).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A thrown fetch in the auth probe is a network failure, not a 401 — it
now retries once before treating the user as logged out, and logs the
real cause (FE-15). Theme and privacy state initialize lazily from
localStorage so neither the wrong theme nor unmasked sensitive values
flash on reload (FE-07). The CopilotKit orphan-tool-call repair logs
when it has to skip instead of failing silently (FE-11).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Transaction create/update/delete now confirm success or failure with a
toast (UX-01); save failures always show a user-visible message instead
of console-only (FE-06, with a typed error guard replacing the any
cast). Clearing a balance override — which recalculates every following
month — now requires confirmation like the other destructive actions
(UX-15).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>