From 620747baa367e986985444211758ceb26f57c217 Mon Sep 17 00:00:00 2001 From: Louis King Date: Thu, 11 Jun 2026 16:48:32 +0100 Subject: [PATCH] fix(web): show node last_seen instead of adopted_at on profile The User Profile page rendered each adopted node's relative time from adopted_at (the adoption date) rather than the node's most recent activity, so it always showed the time since adoption (e.g. "35 days ago") even when the node had advertised today. Expose last_seen on AdoptedNodeRead and render it on the profile page, falling back to "-" when null (matching the nodes/node-detail pages). Co-Authored-By: Claude Opus 4.8 --- src/meshcore_hub/api/routes/user_profiles.py | 1 + src/meshcore_hub/common/schemas/user_profiles.py | 3 +++ src/meshcore_hub/web/static/js/spa/pages/profile.js | 6 +++--- tests/test_api/test_user_profiles.py | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/meshcore_hub/api/routes/user_profiles.py b/src/meshcore_hub/api/routes/user_profiles.py index 1de01ab..057c4ea 100644 --- a/src/meshcore_hub/api/routes/user_profiles.py +++ b/src/meshcore_hub/api/routes/user_profiles.py @@ -46,6 +46,7 @@ def _build_adopted_nodes(profile: UserProfile) -> list[AdoptedNodeRead]: name=assoc.node.name, adv_type=assoc.node.adv_type, adopted_at=assoc.adopted_at, + last_seen=assoc.node.last_seen, ) ) return adopted_nodes diff --git a/src/meshcore_hub/common/schemas/user_profiles.py b/src/meshcore_hub/common/schemas/user_profiles.py index 242ee19..832720a 100644 --- a/src/meshcore_hub/common/schemas/user_profiles.py +++ b/src/meshcore_hub/common/schemas/user_profiles.py @@ -75,6 +75,9 @@ class AdoptedNodeRead(BaseModel): name: Optional[str] = Field(default=None, description="Node display name") adv_type: Optional[str] = Field(default=None, description="Advertisement type") adopted_at: datetime = Field(..., description="When the node was adopted") + last_seen: Optional[datetime] = Field( + default=None, description="Timestamp of the node's most recent activity" + ) class Config: from_attributes = True diff --git a/src/meshcore_hub/web/static/js/spa/pages/profile.js b/src/meshcore_hub/web/static/js/spa/pages/profile.js index aa76970..f74f9b3 100644 --- a/src/meshcore_hub/web/static/js/spa/pages/profile.js +++ b/src/meshcore_hub/web/static/js/spa/pages/profile.js @@ -7,15 +7,15 @@ import { function renderAdoptedNode(node) { const displayName = node.name || node.public_key.slice(0, 12) + '...'; - const relTime = formatRelativeTime(node.adopted_at); - const fullTime = formatDateTime(node.adopted_at); + const relTime = node.last_seen ? formatRelativeTime(node.last_seen) : '-'; + const fullTime = node.last_seen ? formatDateTime(node.last_seen) : '-'; return html`
${displayName}
${node.public_key}
- +
`; } diff --git a/tests/test_api/test_user_profiles.py b/tests/test_api/test_user_profiles.py index da0cb69..7cb4c90 100644 --- a/tests/test_api/test_user_profiles.py +++ b/tests/test_api/test_user_profiles.py @@ -206,6 +206,7 @@ class TestGetProfile: assert len(data["nodes"]) == 1 assert data["nodes"][0]["public_key"] == "abc123def456abc123def456abc123de" assert "adopted_at" in data["nodes"][0] + assert data["nodes"][0]["last_seen"] is not None def test_get_profile_returns_roles(self, client_no_auth, sample_user_profile): """Test that profile includes roles."""