Skip to content

feat: add portfolio activity timeline - #737

Merged
Seranged merged 8 commits into
feature/lite-219-add-activity-events-section-to-vault-page-filterable-byfrom
feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event
Jul 20, 2026
Merged

Seranged merged 8 commits into
feature/lite-219-add-activity-events-section-to-vault-page-filterable-byfrom
feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event

Conversation

@Seranged

@Seranged Seranged commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add Activity as a conditional fourth Portfolio tab after Rewards.
  • Load the effective connected or spy wallet history for the currently selected chain through the shared Activity feed.
  • Keep unavailable V3 data distinct from a real empty account timeline.

Linear: https://linear.app/euler-labs/issue/LITE-220/add-activity-events-tab-to-portfolio-per-wallet-event

Portfolio UX

  • The new route is /portfolio/activity.
  • Tab order is Positions, Rewards, Activity, then Migrate when migration is available.
  • The query owner is useEffectiveAddress(), so spy mode and a connected wallet follow the same path.
  • A disconnected wallet receives an explicit connect-or-use-spy prompt instead of a zero-address request.
  • The account feed exposes the six ticket categories in product order: Lending, Borrowing, Swaps, Liquidations, Account, and Rewards. Governance is intentionally excluded from Portfolio.
  • The shared feed provides loading skeletons, partial/syncing coverage messaging, pagination, retryable errors, transaction links, account/subaccount context, counterparties, vault context, raw amounts, and source-provided valuations.

Availability and fallback behavior

This initial implementation is V3-only. It does not query the subgraph directly.

The tab combines three signals:

  1. The selected chain must have a configured V3 Activity source.
  2. The SDK static capability and account-scope capability check must support the chain.
  3. Runtime coverage from the account events response must not be authoritatively unsupported.

An authoritative unsupported result hides the tab and redirects a direct Activity route back to Portfolio. Runtime support state is scoped by effective owner and chain so a negative result cannot leak across wallets or networks.

Transient capability checks and request failures remain visible as retryable error states. They do not become empty timelines and do not permanently hide the tab. A successful empty response is the only path to the no-indexed-activity message.

The SDK adapter boundary remains the extension point for a future subgraph implementation if product decides that Activity must work on chains without V3.

Enrichment rules

  • V3 owns historical event identity, classification, ordering, coverage, and source-authoritative raw values.
  • Lite adds current token and vault metadata when available.
  • Event-time USD is displayed only when the source provides it.
  • Lite does not reconstruct historical USD from current prices.
  • Missing optional metadata falls back to addresses and raw values without dropping the event.

Cross-repo dependencies and merge order

  1. V3 indexed-event repair: https://github.com/euler-xyz/euler-data-v3/pull/273
  2. V3 normalized Activity API: https://github.com/euler-xyz/euler-data-v3/pull/274
  3. SDK Activity service and adapter contract: feat: add activity service euler-sdks#66
  4. Publish the SDK and bump the Lite Activity stack.
  5. Deploy the normalized V3 Activity API on supported chains.
  6. Lite shared feed and vault Activity: feat: add vault and portfolio activity timelines #736
  7. This Portfolio Activity PR

This PR is stacked on the LITE-219 branch so its diff contains only Portfolio-specific work. Rebase onto development after #736 merges.

Both Lite Activity PRs remain draft until the published SDK is consumed and V3 #274 is deployed on the supported chains.

Known overlap

#718 currently overlaps pages/portfolio.vue and is reported as conflicting. The changes are semantically compatible, but whichever PR lands second needs a manual rebase that preserves both the migration load-error handling and the Activity tab availability watcher.

Current draft CI gate

GitHub lint passes. Typecheck fails only on Activity exports, activityService, and Activity query names absent from the currently published SDK. The remote test job passes 1,281 of 1,282 tests; the sole failure is the SDK query-policy check for those two unpublished Activity query names. Local validation against the built SDK #66 passes typecheck and all 1,282 tests. This gate clears when #66 is published and the Lite dependency is bumped.

Test plan

  • Portfolio runtime-support, Activity availability, shared feed, and display helper suites: 4 files, 28 tests passed.
  • Full Vitest suite: 136 files, 1,282 tests passed.
  • Nuxt typecheck passed against the built SDK PR.
  • Production build passed against the built SDK PR.
  • Changed-file ESLint and commit hooks passed.
  • git diff --check passed.
  • Publish SDK build(deps): bump hono from 4.12.2 to 4.12.5 #66 and bump the Lite dependency.
  • Re-run GitHub typecheck and SDK query-policy CI against the published package.
  • Smoke connected-wallet, spy-mode, unsupported-chain, partial-coverage, empty, error, and load-more states against a deployed V3 Activity API.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7adaf920-7b7c-4a14-aa83-894969f17644

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event

Comment @coderabbitai help to get the list of available commands.

@railway-app

railway-app Bot commented Jul 13, 2026

Copy link
Copy Markdown

🚅 Deployed to the euler-lite-pr-737 environment in euler-lite

Service Status Web Updated (UTC)
dev-build ❌ Build Failed (View Logs) Web Jul 20, 2026 at 9:39 am

@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 61a1bd1 to 48b823c Compare July 13, 2026 14:34
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 13, 2026 14:34 Destroyed
@Seranged
Seranged marked this pull request as ready for review July 17, 2026 08:01
@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 48b823c to caa6faf Compare July 17, 2026 08:33
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 08:33 Destroyed
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 08:59 Destroyed

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Leonard review — changes requested

Reviewed the stacked child delta against feature/lite-219-add-activity-events-section-to-vault-page-filterable-by at 050f2585d4a50a1c834a93b292a5580b15a5c094. The account Activity route, runtime-support isolation, event enrichment, and category request wiring are coherent. Two child-specific UI regressions remain, detailed inline: the replacement category selector is not keyboard/screen-reader operable, and direct mobile navigation leaves the active Activity tab clipped.

Validation

  • Focused Activity/runtime/display tests: 33/33 passed
  • ESLint over changed Activity/portfolio files: passed
  • git diff --check: passed
  • Headed Railway smoke: populated desktop timeline; Borrowing selection updated both the rendered feed and category=borrowing request; 390×844 mobile route and filter exercised
  • Full typecheck/test CI remains blocked by the Activity SDK surface absent from the stack's currently locked @eulerxyz/euler-v2-sdk@1.1.4; the child does not change that dependency, so I treated it as an inherited #736 integration gate rather than a #737-specific finding
  • Security/malicious-code sweep: no concerns in the child delta
  • Scalability/maintainability pass: both fixes belong in the shared UiSelect / UiTabs abstractions, with focused accessibility and mobile direct-route tests, so vault Activity and future tab/filter consumers inherit the correction
  • Bot feedback: CodeRabbit skipped this non-default-base PR; there were no bot findings to adopt or refute

Visual evidence

Desktop, 1440×1000 — Borrowing selected and filtered events rendered:

Desktop Borrowing Activity filter

Mobile, 390×844 — filtered feed works, but the selected Activity tab is clipped at the right edge:

Mobile Borrowing Activity filter

Smoke coverage: browser visual smoke + mobile smoke; no wallet/signing flow was exercised.

{{ option.label }}
</button>
</div>
<UiSelect

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Blocking — restore keyboard and screen-reader operability. This replaces the native category buttons (button + aria-pressed) with UiSelect, but the shared trigger renders as a clickable div with no role, focusability, keyboard handler, or expanded state. In the headed preview DOM it was role=null and tabIndex=-1, so a keyboard user cannot open this filter. The modal's checkbox inputs are also not associated with the adjacent option text. Because this component feeds both account and vault Activity, please fix the shared UiSelect trigger/options semantics (or retain native buttons) and add a focused keyboard/accessibility test.

Comment thread pages/portfolio.vue

if (showActivityTab.value) {
items.push({
label: 'Activity',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mobile regression — ensure the route-selected tab is scrolled fully into view. At 390px, direct navigation to /portfolio/activity leaves this active label visibly clipped at the right edge. UiTabs scrolls only from onSelect; initial routes, browser history, and programmatic currentIdx changes only update the active block. Please fix this in shared UiTabs on mount/current-index/list changes and cover a narrow direct-route case, rather than adding a portfolio-only offset.

@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 09:37 Destroyed
@LeonardEulerXYZ

LeonardEulerXYZ commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Leonard review — current head 930442db

Verdict: changes requested. The latest presentation commits improve the timeline materially: the preview now renders a focused position history with market, underlying amount, position/participant, and transaction context. The two existing user-facing blockers remain on the current head, however.

Blocking

  1. Activity filtering is still not keyboard or screen-reader operable. ActivityCategoryFilters now depends on UiSelect, whose trigger is a click-only <div> with no role, focusability, keyboard handler, or expanded state. The option text is also separate from the checkbox's accessible label. Current preview DOM: tag=DIV, role=null, tabIndex=-1. The existing inline thread remains applicable.

  2. Direct mobile navigation still clips the active Activity tab. At 390 px, /portfolio/activity starts with the right edge of Activity outside the viewport and Migrate off-screen. UiTabs reveals tabs only from onSelect; mount and route-driven currentIdx changes update the active block but do not scroll the active tab into view. The existing inline thread remains applicable.

Current-head review notes

  • Compared the full child delta against parent branch feature/lite-219-add-activity-events-section-to-vault-page-filterable-by / PR feat: add vault and portfolio activity timelines #736, plus the incremental delta from the previously reviewed 8158e496 head.
  • The position-focused event presentation and filtering request are coherent in the deployed fixture. Selecting Borrowing produced a new account-events request with category=borrowing; no page exceptions or material console errors were observed.
  • CI evidence: lint and the Railway dev build pass. The test job passes 1,293/1,294 tests; its sole failure is the declared unpublished-SDK query-policy gate. Typecheck fails on Activity exports/service/query names absent from the currently pinned public SDK, matching the documented cross-repo sequencing gate. A fresh local install was not run because this head's package manifest and lockfile do not currently satisfy npm ci.
  • Scalability / maintainability hygiene: portfolio and vault timelines correctly reuse the shared feed, event row, query composable, and display utilities. Runtime unsupported state is owner+chain scoped and covered by focused tests. The remaining regressions belong in the shared UiSelect and UiTabs primitives, not portfolio-only offsets or handlers.
  • PR-description hygiene: the summary still says Portfolio exposes six categories including Swaps and Rewards, while the current head intentionally requests four focused categories: Lending, Borrowing, Liquidations, and Position. Please align the description with the shipped scope.
  • Bot-feedback pass: CodeRabbit skipped this stacked-base review and supplied no material claim to adopt or refute.

Smoke coverage

Desktop browser visual + filter interaction + mobile visual on the current Railway preview, using a public spy-mode fixture. Wallet/signing smoke not applicable.

Desktop — populated position activity with market, amount, participant, and transaction context:

PR 737 desktop activity

Mobile 390×844 — populated feed; the active Activity label is clipped at the right edge and Migrate is outside the initial viewport:

PR 737 mobile activity

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed current head 8158e496827da7882748374e5397400b435d2292.

The new event-presentation work is coherent in focused tests and desktop/mobile preview smoke, but the two current-head inline blockers remain:

  • the shared activity filter trigger is a mouse-only <div> with no keyboard semantics;
  • the fixed six-column portfolio tab row clips Activity and hides Migrate at 390 px.

The current-head inline threads contain the concrete findings. I also posted a walkthrough with validation and verified raster screenshot evidence. No additional blocking issue was found in the 8158e496 presentation delta itself.

Validation: 4 focused activity test files / 33 tests passed; scoped ESLint passed; headed Chromium desktop + 390 px mobile smoke exercised the Borrowing filter and populated feed. Local Nuxt typecheck was inconclusive because the cached local SDK predates this stacked branch's activity exports; GitHub's current-head typecheck/test checks are green.

Smoke coverage: desktop browser visual + filter interaction + mobile visual. Wallet/signing not applicable.

@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 8158e49 to 257dac9 Compare July 17, 2026 10:14
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 10:14 Destroyed
@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 257dac9 to ff196eb Compare July 17, 2026 10:31
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 10:32 Destroyed
@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from ff196eb to 53cc658 Compare July 17, 2026 10:45
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 10:45 Destroyed
@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 53cc658 to 5c5486c Compare July 17, 2026 10:58
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 10:58 Destroyed
Seranged added 7 commits July 17, 2026 12:03
- clarify transfers and suppress exact paired share movements
- add token imagery, compact responsive details, and copyable addresses
- hide unavailable pricing noise while preserving truthful fallbacks
Describe incomplete categories without implying that no events exist.
Limit position history to attributable operations, show asset-unit amounts, and conservatively collapse exact paired events.
Align portfolio activity copy with the position terminology used throughout Lite.
@Seranged
Seranged force-pushed the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch from 5c5486c to 930442d Compare July 17, 2026 11:04
@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 17, 2026 11:04 Destroyed

@LeonardEulerXYZ LeonardEulerXYZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed current head 930442db1f41620d7db09dfefb51541892d65913 as the stacked child delta against feature/lite-219-add-activity-events-section-to-vault-page-filterable-by / PR #736, plus the incremental delta from the previously reviewed 8158e496 head.

The latest position-history presentation is coherent in the deployed desktop/mobile fixture, and the Borrowing filter issues the expected category-scoped account-events request. Two current-head blockers remain in the existing inline threads:

  • the Activity category selector is not keyboard or screen-reader operable because the shared UiSelect trigger is an unfocusable click-only <div> and its option text does not name the checkbox;
  • direct /portfolio/activity navigation at 390 px leaves the active Activity tab clipped because shared UiTabs does not reveal route/model-driven selections.

Validation:

  • git diff --check passed on the child delta;
  • GitHub lint and Railway dev build pass;
  • GitHub test: 1,293/1,294 pass; the sole failure is the declared unpublished-SDK query-policy gate;
  • GitHub typecheck fails only on Activity exports/service/query names absent from the pinned public SDK;
  • headed Chromium preview smoke at 1440×1000 and 390×844: populated activity rows, Borrowing filter interaction/request verified, no page exceptions or material console errors;
  • scalability/maintainability pass: shared feed/display/runtime-support abstractions are reused appropriately; the remaining fixes should land in shared UiSelect and UiTabs rather than portfolio-only workarounds;
  • CodeRabbit skipped the stacked-base review and supplied no actionable finding.

The updated Leonard walkthrough contains current-head screenshot evidence and the exact validation caveats.

@railway-app
railway-app Bot temporarily deployed to euler-lite / euler-lite-pr-737 July 20, 2026 09:36 Destroyed
@Seranged
Seranged merged commit 6b86ec8 into feature/lite-219-add-activity-events-section-to-vault-page-filterable-by Jul 20, 2026
2 of 5 checks passed
@Seranged
Seranged deleted the feature/lite-220-add-activity-events-tab-to-portfolio-per-wallet-event branch July 20, 2026 09:52
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.

2 participants