diff --git a/e2e/USER_STORIES.md b/e2e/USER_STORIES.md
index 188ab922..aeaaebe8 100644
--- a/e2e/USER_STORIES.md
+++ b/e2e/USER_STORIES.md
@@ -1144,6 +1144,8 @@ Acceptance:
- His profile shows display name, avatar, banner, about text, and a shortened
npub whose copy button confirms the copy.
+- A status he has published shows what he is up to, and links to the url it
+ names; an expired one shows nothing.
- A website he has set renders as a link.
- A Spaces panel lists the spaces he belongs to with a count, marks any alice
also belongs to as "Member", and navigates to a space when clicked; with none,
@@ -1214,7 +1216,7 @@ check who someone is without losing my place.
Acceptance:
- Clicking bob's avatar or mention in a member list or message opens a popover
- with his avatar, name, about text, and badges.
+ with his avatar, name, about text, status, and badges.
- The popover offers "View Full Profile", which navigates to his profile page.
- Closing the popover leaves alice where she was.
diff --git a/e2e/specs/people.spec.ts b/e2e/specs/people.spec.ts
index 0b05356c..b5035a16 100644
--- a/e2e/specs/people.spec.ts
+++ b/e2e/specs/people.spec.ts
@@ -2,7 +2,7 @@ import {createHash} from "node:crypto"
import type {Locator, Page} from "@playwright/test"
import {nprofileEncode, npubEncode} from "nostr-tools/nip19"
import {HOUR, MINUTE, MONTH} from "@welshman/lib"
-import {NOTE, makeEvent} from "@welshman/util"
+import {NOTE, STATUS, makeEvent} from "@welshman/util"
import type {SignedEvent} from "@welshman/util"
import {FollowList, Note, PinList, Profile} from "@welshman/domain"
import {
@@ -142,7 +142,7 @@ test("US-075 view someone's profile", async ({seed, as}) => {
const avatar = "https://images.test/bob-avatar.png"
const banner = "https://images.test/bob-banner.png"
- const scenario = await seed(({relay, user}) => {
+ const scenario = await seed(({relay, user, at}) => {
const space = relay("space")
const other = relay("other")
@@ -167,11 +167,34 @@ test("US-075 view someone's profile", async ({seed, as}) => {
.renderTemplate(),
)
+ space.event(
+ user.bob,
+ makeEvent(STATUS, {
+ content: "Scrubbing the decks",
+ tags: [
+ ["d", "general"],
+ ["r", "https://bobbarnacle.example/decks"],
+ ],
+ }),
+ )
+
space.relayList(user.bob)
// Carol belongs to no space at all, which is what the panel's empty state is about.
space.member(user.carol)
space.profile(user.carol, {name: "Carol Cutter"})
+ // An expired status is one its author asked relays to stop serving, so it is not what she is
+ // up to any more.
+ space.event(
+ user.carol,
+ makeEvent(STATUS, {
+ content: "Ashore until spring",
+ tags: [
+ ["d", "general"],
+ ["expiration", String(at(1, HOUR))],
+ ],
+ }),
+ )
space.relayList(user.carol)
})
@@ -185,6 +208,10 @@ test("US-075 view someone's profile", async ({seed, as}) => {
await expect(region.locator(`img[src="${avatar}"]`).last()).toBeVisible()
await expect(region.locator(`img[src="${banner}"]`)).toBeVisible()
await expect(region.getByText("Deckhand, dockside cook")).toBeVisible()
+ await expect(region.getByRole("link", {name: "Scrubbing the decks"})).toHaveAttribute(
+ "href",
+ "https://bobbarnacle.example/decks",
+ )
const npub = region.getByText(shortNpub(users.bob))
@@ -217,6 +244,7 @@ test("US-075 view someone's profile", async ({seed, as}) => {
await expect(page.getByRole("heading", {name: "Carol Cutter"})).toBeVisible()
await expect(sidebar(page).getByText("No spaces found.")).toBeVisible()
+ await expect(region.getByText("Ashore until spring")).toHaveCount(0)
})
test("US-076 follow and unfollow", async ({seed, as}) => {
@@ -554,6 +582,10 @@ test("US-080 preview a profile from anywhere", async ({seed, as}) => {
picture: avatar,
})
space.message(user.bob, "general", "the cat has the helm", at(1, HOUR))
+ space.event(
+ user.bob,
+ makeEvent(STATUS, {content: "Scrubbing the decks", tags: [["d", "general"]]}),
+ )
space.relayList(user.bob)
})
@@ -576,6 +608,7 @@ test("US-080 preview a profile from anywhere", async ({seed, as}) => {
await expect(topDialog(page).getByText("Bob Barnacle")).toBeVisible()
await expect(topDialog(page).locator(`img[src="${avatar}"]`)).toBeVisible()
await expect(topDialog(page).getByText("Deckhand, dockside cook")).toBeVisible()
+ await expect(topDialog(page).getByText("Scrubbing the decks")).toBeVisible()
await expect(topDialog(page).getByText(/Last active/)).toBeVisible()
await page.keyboard.press("Escape")
diff --git a/src/app/components/ProfileDetail.svelte b/src/app/components/ProfileDetail.svelte
index 3c3b6f68..9600e038 100644
--- a/src/app/components/ProfileDetail.svelte
+++ b/src/app/components/ProfileDetail.svelte
@@ -17,6 +17,7 @@
import ProfileAbout from "@app/components/ProfileAbout.svelte"
import ProfileBadges from "@app/components/ProfileBadges.svelte"
import ProfileMenu from "@app/components/ProfileMenu.svelte"
+ import ProfileStatus from "@app/components/ProfileStatus.svelte"
import {messagingRelayLists, profiles, relayManagement, user} from "@app/core"
import {deriveUserIsSpaceAdmin} from "@app/management"
import {navigate, popModal, pushModal} from "@app/modal"
@@ -134,6 +135,7 @@
{/snippet}
+