Skip to content

feat: group account operations by transaction with direction badges#27

Open
xdefrag wants to merge 2 commits intomasterfrom
feat/grouped-operations
Open

feat: group account operations by transaction with direction badges#27
xdefrag wants to merge 2 commits intomasterfrom
feat/grouped-operations

Conversation

@xdefrag
Copy link
Copy Markdown
Contributor

@xdefrag xdefrag commented Apr 9, 2026

Summary

  • Adjacent operations sharing a transaction now render as a native <details> card with the first op as the clickable summary and +N more badge; expanding reveals the memo + remaining ops + "view tx →" link.
  • Payment-like ops where the viewed account is a party show ↓ IN (green) or ↑ OUT (amber) badges with the counterparty name, making "who sent what to whom" immediately scannable.
  • Pagination now counts transactions, not operations: cursor is the paging token of the last op of the last complete group, so transactions are never cut mid-flight across pages.

Technical details

  • Added Join: "transactions" to the Horizon operations request — memo/source/success data comes inline at zero extra HTTP cost.
  • New service method GetAccountTransactionGroups replaces GetAccountOperations; the grouping state machine includes deadlock guards (force-flush partial group if safety caps fire before any group completes; never return HasMore=true with an empty cursor).
  • Typed model.OperationDirection with constants (DirectionIn, DirectionOut, DirectionNone) + IsValid(), per the project's constrained-type convention.
  • New dict template helper enables an op-body partial that's shared across the single-op row, the multi-op summary row, and each nested sub-op — DRY across all rendering paths.
  • Template safety guards against empty groups and non-nil empty TxGroups (no spurious section header).
  • CSS is pure dark-retro-terminal style: no JS, no page-load animations (chevron rotates only on [open] state via user click).

Test plan

  • go test ./... — all passing
  • go vet ./... — clean
  • gofmt -l . — clean
  • New unit tests: TestSetOperationDirection (12 cases), TestOperationDirectionIsValid, template render tests for multi-op groups and empty-groups case
  • Manual: visit an account with multi-op transactions and verify the <details> expand behavior
  • Manual: verify IN/OUT badges appear with correct counterparty on both incoming and outgoing payments
  • Manual: paginate via "Load More" and confirm the cursor advances by transactions, not ops
  • Manual: verify mobile layout (the time column hides on <768px)

🤖 Generated with Claude Code

xdefrag and others added 2 commits April 9, 2026 18:58
Operations on the account detail page are now grouped by parent transaction
using collapsible <details>, with IN/OUT direction badges on payment-like ops
showing the counterparty. Pagination counts transactions (up to 10 per page)
instead of operations, and memo is surfaced inline via Horizon's join=transactions
parameter (zero extra round-trips).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical:
- Fix end-of-history pagination data loss: track reachedLimit to
  correctly report HasMore when breaking out of the loop on a partial
  trailing page (previously, remaining records in that page became
  unreachable via "Load More").
- Remove dead `|| currentGroup != nil` from hasMore computation; the
  reconcile block already clears currentGroup unconditionally.
- Clarify the function doc: the empty-NextCursor invariant is a
  guarantee of the function, not a burden on callers.
- Extract operationFetcher interface + fetchTransactionGroups helper to
  make the grouping state machine directly unit-testable. Add
  comprehensive table-driven tests: empty stream, single/multi-op
  groups, groupLimit boundary, blockchain exhausted, all-spam drop,
  mixed spam kept, force-flush on cap, all-spam deadlock guard, error
  propagation, cursor passthrough, LedgerCloseTime-as-CreatedAt.

High:
- Spam filter now operates at TRANSACTION level: a group is dropped
  only if every op is spam-classified. Mixed groups preserve all ops
  so the "+N more" count matches the transaction detail page.
- Promote force-flush log to slog.Warn with tx_hash and partial_op_count
  so operators see the event in production.
- Add slog.Warn for the all-spam-window deadlock guard.
- Log slog.Warn when join=transactions returns no Transaction data
  (unexpected SDK/Horizon regression).
- Handler now surfaces operations fetch errors via TxGroupsError field
  rendered as an inline notice, instead of silently showing an empty
  section. Error log now includes ops_cursor for triage.
- TransactionGroup gains a Partial bool flag for force-flushed groups;
  template renders a PARTIAL badge and explanatory note.
- Document the TransactionGroup.Operations non-empty invariant.
- Add doc comment to dict template helper; add error-path tests for
  odd-arg-count and non-string-key cases.

Medium:
- Add DirectionSelf constant for self-payments (viewer on both sides);
  template renders "↻ SELF" badge with purple theme.
- Render failed-transaction badge on both single-row and multi-op
  groups; add op-group-failed CSS class with reduced opacity.
- Use base.Transaction.LedgerCloseTime (from the join) for the group's
  CreatedAt instead of the first op's timestamp.
- Add account_merge branch to op-amount partial so directional merges
  render "(full balance)" instead of an empty gap.
- Move op-body doc comment to the correct position above its define.
- Render the transaction source account in the expanded group meta
  when it differs from the viewer, so collectGroupAccountIDs' name
  lookup is not wasted.
- Add memo-absent path fallback: if payment has empty From/To and no
  direction, render "(missing party)" instead of silently eliding.
- Add TestCollectGroupAccountIDs covering dedup across groups,
  DataValue-as-stellar-ID, and group-level SourceAccount.
- Add template tests for: failed badge, partial badge, error notice,
  self-direction badge.

Low:
- Add doc comment to addOpIDs.
- Document OperationDirection zero-value decision and IsValid() scope.
- Document Horizon-order invariant on the group-boundary check.
- Restore "Newest first" comment on OrderDesc.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant