Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
orderBy = "last_used_at IS NULL, " + orderBy
}

if orderBy == "last_settled_transaction" {
query = query.Select("apps.*, MAX(transactions.settled_at) as last_transaction_at").
Joins("LEFT JOIN transactions ON transactions.app_id = apps.id AND transactions.state = ?", constants.TRANSACTION_STATE_SETTLED).
Group("apps.id")
orderBy = "last_transaction_at IS NULL, last_transaction_at DESC, apps.last_used_at"
}

query = query.Order(orderBy + " DESC")

if limit == 0 {
Expand Down
40 changes: 17 additions & 23 deletions frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ALBY_ACCOUNT_APP_NAME } from "src/constants";
import { useApps } from "src/hooks/useApps";

export function LatestUsedAppsWidget() {
const { data: appsData } = useApps(3, undefined, undefined, "last_used_at");
const { data: appsData } = useApps(3, undefined, undefined, "last_settled_transaction");
const apps = appsData?.apps;
const usedApps = apps?.filter((x) => x.lastUsedAt);
Comment on lines +16 to 18
Copy link
Contributor

@coderabbitai coderabbitai bot Mar 23, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't keep keying this widget off lastUsedAt.

This now asks the API for last_settled_transaction, but it still filters rows and renders the relative-time label from lastUsedAt, which the PR explicitly keeps as connection activity. A recent balance check can therefore still make an app look newer than its last paid action, and the post-limit filter can leave the widget under-filled. Please plumb a dedicated lastSettledTransaction field through the API and use that here instead.

Also applies to: 35-45

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/home/widgets/LatestUsedAppsWidget.tsx` around lines
16 - 18, The widget is still keyed and filtered by lastUsedAt despite requesting
last_settled_transaction from the API; update LatestUsedAppsWidget to use the
API's lastSettledTransaction field instead: change the useApps consumer to read
appsData?.apps[*].lastSettledTransaction (not lastUsedAt), update usedApps =
apps?.filter(x => x.lastSettledTransaction), and render the relative-time label
from lastSettledTransaction; also make the same replacement for the other
occurrences in this component (around the 35-45 region) so filtering, sorting,
and display consistently use lastSettledTransaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The lastUsedAt filter just checks if the app has connected before, the sorting itself comes from the backend. Can add a dedicated field in a follow-up if needed!

Copy link
Contributor

Choose a reason for hiding this comment

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

@keshav0479, that's a fair point — using lastUsedAt as a gate to filter out apps that have never connected at all is reasonable, and the backend-driven sorting by settled transactions is the most impactful fix here.

The one remaining UX concern is the displayed time label (dayjs(app.lastUsedAt).fromNow()): a user seeing "2 minutes ago" next to an app that last did a balance check (but hasn't moved value in days) could still be misled. That said, since the ordering itself is now correct, this is a cosmetic/UX issue — a follow-up to plumb lastSettledTransaction through to the frontend for the label sounds like a pragmatic approach. Would you like to open a follow-up issue to track that?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be fixed in this PR. If we filter by last economic activity, then we should also show when the last economic activity was, not the last time it was just accessed via NWC.

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


Expand All @@ -32,28 +32,22 @@ export function LatestUsedAppsWidget() {
</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 gap-4">
{usedApps
.sort(
(a, b) =>
new Date(b.lastUsedAt ?? 0).getTime() -
new Date(a.lastUsedAt ?? 0).getTime()
)
.map((app) => (
<Link key={app.id} to={`/apps/${app.id}`}>
<div className="flex items-center w-full gap-4">
<AppAvatar app={app} className="w-14 h-14 rounded-lg" />
<p className="text-sm font-medium flex-1 truncate">
{app.name === ALBY_ACCOUNT_APP_NAME
? "Alby Account"
: app.name}
</p>
<p className="text-xs text-muted-foreground">
{app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"}
</p>
<ChevronRightIcon className="text-muted-foreground size-8" />
</div>
</Link>
))}
{usedApps.map((app) => (
<Link key={app.id} to={`/apps/${app.id}`}>
<div className="flex items-center w-full gap-4">
<AppAvatar app={app} className="w-14 h-14 rounded-lg" />
<p className="text-sm font-medium flex-1 truncate">
{app.name === ALBY_ACCOUNT_APP_NAME
? "Alby Account"
: app.name}
</p>
<p className="text-xs text-muted-foreground">
{app.lastUsedAt ? dayjs(app.lastUsedAt).fromNow() : "never"}
</p>
<ChevronRightIcon className="text-muted-foreground size-8" />
</div>
</Link>
))}
</CardContent>
</Card>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function useApps(
unused?: boolean;
subWallets?: boolean;
},
orderBy?: "last_used_at" | "created_at",
orderBy?: "last_used_at" | "created_at" | "last_settled_transaction",
isEnabled = true
) {
const offset = (page - 1) * limit;
Expand Down
2 changes: 1 addition & 1 deletion nip47/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, pool nostrmodels.Simpl
err = svc.db.Model(&app).Update("last_used_at", &now).Error
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"it": app.ID,
"app_id": app.ID,
}).WithError(err).Error("Failed to update app last used time")
}

Expand Down
Loading