Skip to content

History Mini App #125

Description

@madhead

Finish the half-built History Mini App — a Telegram Mini App that lists a group's shared expenses with live, debounced, per-field search and infinite pagination. Launched via the group-bound, HISTORY-scoped, 30-min deep-link token minted by /history.

This is the next thing to build. The visible half is the frontend, but the bigger lift is the backend: search is currently a stub.

Backend

  • Real search. Today TransactionsSearchParams has only title, and TransactionsSearchService.search() ignores it — it returns every transaction for the group. Implement actual filtering in the TransactionRepository Postgres adapter via a dynamic WHERE (all filters optional; empty = match all):
    • title — case-insensitive substring (ILIKE)
    • participant(s) — "involves member": payer OR present in recipients; multi-select, any-of
    • amount — min/max range (only meaningful together with a currency)
    • currency — exact match (from the group's used currencies)
    • date — from/to range on timestamp (stored UTC)
  • Pagination — keyset/cursor, not OFFSET (degrades on deep history). ORDER BY timestamp DESC, id DESC; cursor = last-seen (timestamp, id); return a next-cursor. Page size ~30.
  • API wiring. Thread query params through GET /app/api/group/transactions?title=&currency=&amountMin=&amountMax=&from=&to=&participant=&cursor= into TransactionsSearchParams. Keep the existing BigDecimal/Instant serializers.

Frontend

  • Filter bar — one control per field, no Search button; search fires on edit, debounced (~300ms).
  • Infinite scrollIntersectionObserver sentinel; append pages by cursor.
  • Transaction widget rework:
    • Resolve payer + recipient IDs → names via GET /app/api/group/members (fetch once, cache in memory).
    • Render payer, recipients (the .participants area is currently empty), amount, currency, title, date.
    • Replace hand-rolled Amount.formatAmount with Intl.NumberFormat (locale + currency aware).
  • States — loading, empty, and error states + an ErrorBoundary (today HistoryApp does setTransactions(await res.json()) with no handling).
  • LifecycleWebApp.ready() (dismiss the Telegram splash) and WebApp.expand() (full viewport).

Digested from #294 (Mini App findings)

  • Transaction.tsx doesn't render participants — the .participants div is empty, so history cards never show who paid / who owes (needs /group/members to resolve IDs → names).
  • group/members endpoint has no client caller (consumed here, for names). The frontend only calls auth/validation and group/transactions; group/members and group/currencies have no client callers.
  • No loading / error / empty states, no error boundary. HistoryApp.tsx does setTransactions(await response.json()) with no error handling (failed/non-JSON response throws unhandled); no loading indicator, no empty state, no ErrorBoundary. AppWrapper shows a blank screen (null) while auth is in flight.
  • Missing Mini App lifecycle calls. No WebApp.ready() (dismiss Telegram's loading placeholder) and no WebApp.expand() (full viewport). No MainButton/BackButton integration (MainButton is the idiomatic submit affordance for the future expense form).
  • Amount.formatAmount hand-trims trailing zeros with string slicing and is locale-blind — prefer Intl.NumberFormat.

Open questions

  • Participant filter: single "involves" selector (default) vs separate payer/recipient filters?
  • Amount filter UX: force a currency when an amount range is set? (cross-currency amounts aren't comparable)
  • Final page-size / debounce values
  • Sort: newest-first only, or expose sort options?

Notes / technical details

  • Token TTL is by design. Deep-linked apps keep the short 30-min token (indefinite tokens would be bad). Expiry surfaces as 401 + X-Auth-Error: token_expired, which the FE already maps to a "reopen" message — no refresh path for deep-linked launches. Some sort of an HTTP interceptor might be installed, so that if a Mini App makes an HTTP request with an expired token, it is cleared (like the AppWrapper does on init).
  • Language mismatch (deferred). i18n currently keys off the user's Telegram language_code while the bot uses GroupConfig.language, so the app and bot can render different languages. Solvable later (we know user↔group at auth time and can return the group language). Not in scope here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions