From 1bd39335b2726dc26daabaf9be959c42c4784fd5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 3 Aug 2026 15:05:23 +0000 Subject: [PATCH] docs(commons): use UserLine for poll voter rows, show NIP-05 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserLine (ShowUserSuggestionList.kt:185) is already the row this needs: a SlimListItem with ClickableUserPicture / UsernameDisplay / WatchAndDisplayNip05Row, and it already exposes a nullable trailingContent parameter. So the results screen writes no row and modifies no existing composable — it passes the option label and timestamp into the slot that is already there. Drops the previously proposed trailingContent addition to UserCompose. The second line is now the NIP-05 identifier rather than the about text, drawn the way the app draws it: local part, verified mark, domain, no @, with the local part ellipsizing and the domain left visible. A root identifier shows the domain alone; the npub appears only as the no-NIP-05 fallback. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AQorYBSv9oMvKa8hF1uGBv --- commons/plans/2026-08-03-poll-results-page.md | 86 +++++++++--------- .../assets/2026-08-03-poll-results-page.html | 87 +++++++++++++------ 2 files changed, 104 insertions(+), 69 deletions(-) diff --git a/commons/plans/2026-08-03-poll-results-page.md b/commons/plans/2026-08-03-poll-results-page.md index d2ad3a2cdf..f67990c6ec 100644 --- a/commons/plans/2026-08-03-poll-results-page.md +++ b/commons/plans/2026-08-03-poll-results-page.md @@ -191,11 +191,11 @@ more than 100 responses truncates silently. ├─────────────────────────────────────────────┤ │ [All] [nos.lol] [damus] [purplepag.es] │ option filter chips ├─────────────────────────────────────────────┤ -│ ⬤ Ana Reis nos.lol 2d ago │ voter rows — SlimListItem, -│ Building things on nostr. │ same anatomy as UserCompose -│ ⬤ Bruno Sá damus 2d ago │ (§6.1): picture / name / -│ Relay operator, Lisbon. │ about, vote in the trailing -│ ⬤ npub1q8f…j3xw damus 22h ago│ slot +│ ⬤ Ana Reis nos.lol 2d ago │ voter rows — UserLine as-is +│ ana ✓ nostrcheck.me │ (§6.1): picture / name / +│ ⬤ Bruno Sá damus 2d ago │ NIP-05, vote passed in as +│ bruno ✓ nostrplebs.com │ trailingContent +│ ⬤ npub1q8f…j3xw damus 22h ago│ (npub = no-NIP-05 fallback) │ … │ ├─────────────────────────────────────────────┤ │ 380 of ~412 loaded from 6 relays · 3 ignored│ completeness footer (§4) @@ -211,7 +211,7 @@ version** — see §5.3. |---|---|---| | Percent basis | single-choice: % of voters; multi-choice: % of voters, bars may exceed 100% in sum, each row captioned "N of M voters" | §3.1 — matches how people read a poll | | Voter list default | flat list of **all** voters, each row showing which option(s) they chose | answers "who voted for what" in one screen without drilling | -| Voter row | `SlimListItem` with the same slots `UserCompose` fills — `UserPicture` / `UsernameDisplay` / `AboutDisplay` — and the vote in `trailingContent` (§6.1) | a voter is a user; it should look like every other user row in the app | +| Voter row | `UserLine` unmodified, with the vote passed as its existing `trailingContent` (§6.1) | a voter is a user; the app already has this row, and it already has the slot | | Option chips | filter the voter list to one option; the summary block never filters | keeps totals stable while browsing | | Muted users | excluded by default via `account.isHidden`, with a footer line "2 hidden by your mute list" | consistent with every other user list in the app | | Ordering | you first, then follows, then pubkey — today's `filterTo` comparator, no sort control | the ordering already exists and already does the useful thing | @@ -239,49 +239,53 @@ and the option chips are the only filter. Following `commons/ARCHITECTURE.md` (state + ViewModels + shared Compose in `commons`, screens/nav platform-native): -### 6.1 The voter row is an existing user row +### 6.1 The voter row is `UserLine`, unmodified -A voter is a user, and the app already has one way to draw a user in a list. -`UserCompose` (`amethyst/…/ui/note/UserCompose.kt:52`) is `SlimListItem` -(`ui/layouts/listItem/SlimListItemLayout.kt:168`) with four slots filled: +A voter is a user, and the app already has a user row with a trailing slot: +**`UserLine`** (`amethyst/…/ui/note/creators/userSuggestions/ShowUserSuggestionList.kt:185`), +the row the mention autocomplete and the follow-import screens use. It is +`SlimListItem` (`ui/layouts/listItem/SlimListItemLayout.kt:168`) with: | Slot | Filled with | Renders | |---|---|---| -| `leadingContent` | `UserPicture(user, Size55dp, …)` | avatar, with its own loading/blank handling | -| `headlineContent` | `UsernameDisplay(user, accountViewModel)` | the account's petname → `bestName()` → `pubkeyDisplayHex()`, with custom-emoji support via `CreateTextWithEmoji` | -| `supportingContent` | `AboutDisplay(user, accountViewModel)` | the profile's about text, one line, ellipsized, in `placeholderText` | -| `trailingContent` | `UserActionOptions(…)` | follow/unfollow + list buttons | - -The results row wants the first three **verbatim** and only differs in the -fourth, where the vote goes instead of the follow buttons. So don't write a new -row — add a slot to the existing one: +| `leadingContent` | `ClickableUserPicture(user, Size55dp, …)` | avatar | +| `headlineContent` | `UsernameDisplay(user, accountViewModel)` | petname → `bestName()` → `pubkeyDisplayHex()`, with custom-emoji support via `CreateTextWithEmoji` | +| `supportingContent` | `WatchAndDisplayNip05Row(user, accountViewModel)` | the NIP-05 identifier — local part, verified symbol, domain — in `colorScheme.nip05` | +| `trailingContent` | **caller-supplied, already `null`-able** | — | ```kotlin -@Composable -fun UserCompose( +fun UserLine( baseUser: User, - modifier: Modifier = Modifier, accountViewModel: AccountViewModel, - nav: INav, - trailingContent: @Composable () -> Unit = { UserActionOptions(baseUser, accountViewModel, nav) }, -) { … } + trailingContent: (@Composable (User) -> Unit)? = null, + colors: ListItemColors = ListItemDefaults.colors(), + onClick: () -> Unit, +) ``` -Every existing call site is unchanged by the default; the poll screen passes the -option label + relative timestamp. This also means voter rows inherit, for free, -the things a hand-rolled row would silently lose: petname overrides, emoji in -display names, the npub fallback when metadata hasn't arrived, and the -tap-to-profile navigation from `routeFor(user)`. +So the results screen writes **no row at all** and modifies **no existing +composable** — it calls `UserLine` and passes the option label plus the relative +timestamp as `trailingContent`. Voter rows then inherit for free the things a +hand-rolled row silently loses: petname overrides, emoji in display names, live +NIP-05 verification state, and the npub fallback when nothing else has arrived. Two consequences worth stating: -- **The second line is the user's about text, not their npub.** That is what the - rest of the app shows, and the npub already appears in the headline when there - is no metadata to show instead. -- **No bespoke "follows" or "you" chip.** `UserCompose` has never had one, and the +- **The second line is the NIP-05 identifier, not the npub.** `WatchAndDisplayNip05Row` + (`:214`) renders `pubkeyDisplayHex()` only in the `else` branch, when the user + has no NIP-05 — it is a fallback, not the intended content. Note the app draws + it as *name · verified symbol · domain* with no `@` (`:236-258`), and the + `hasLocalPart()` check means a root identifier (`_@domain`) shows the domain + alone. +- **No bespoke "follows" or "you" chip.** No user row in the app has one, and the ordering (you → follows → rest) already carries that information. If a follow - marker is wanted later it should come from the existing follow-state renderer - used elsewhere, not a new badge invented for this screen. + marker is wanted later it should come from the existing follow-state renderer, + not a badge invented for this screen. + +The one loose end: `UserLine` lives under `note/creators/userSuggestions/`, a +mention-autocomplete package, despite being a general-purpose row with three +callers already. Moving it to `ui/note/` is a tidy-up this change makes +worthwhile but does not require. ### 6.2 New and edited files @@ -316,8 +320,6 @@ Two consequences worth stating: **Edited (entry points):** -- `UserCompose.kt` — add the `trailingContent` slot (§6.1). Default keeps every - existing call site identical. - `Poll.kt` — make the totals line and the `UserGallery` `+N` chip clickable → `nav.nav(Route.PollResults(noteId))`; add a "N votes" count next to the percentage (currently absent). @@ -361,7 +363,7 @@ very different tally models. |---|---|---| | 0 | Poll-aware tally (§3) — commons + tests | yes — fixes the inline card's numbers on both platforms | | 1 | Shared subscription + backfill (§4) | yes — Android tallies stop being short | -| 2 | `PollResultsViewModel` + shared composables + `UserCompose` trailing slot + Android screen + route + entry points | yes — the feature | +| 2 | `PollResultsViewModel` + shared composables + Android screen + route + entry points | yes — the feature | | 3 | Desktop column | parity | | 4 | Audience filter, sort, voter search (§5.3) | once it's clear which are actually reached for | | 5 | Zap polls (§7) | breadth | @@ -379,9 +381,9 @@ bug fixes wearing a feature's clothes. (`Poll.kt:298-336`, `hasViewedPollResults`). Proposal: the page respects the same gate — reaching it counts as opting in and calls `markPollResultsViewed`, exactly like today's "View results" link. -3. **Trailing slot vs. a second entry point.** §6.1 adds a `trailingContent` - parameter to `UserCompose` with a default that preserves every call site. The - alternative is a separate `UserComposeWithTrailing` — more files, no behaviour - difference. Flagging it because it edits a composable used across the app. +3. **Move `UserLine` out of `userSuggestions/`?** It is a general-purpose row + sitting in a mention-autocomplete package (§6.1). The poll screen is its + fourth caller. Move it to `ui/note/` with this change, or leave it and + import across packages? 4. **Export/share results** (copy as text, or a rendered image) — desirable, but listed as out of scope until the page exists. diff --git a/commons/plans/assets/2026-08-03-poll-results-page.html b/commons/plans/assets/2026-08-03-poll-results-page.html index 2697e5eb6f..df227501d0 100644 --- a/commons/plans/assets/2026-08-03-poll-results-page.html +++ b/commons/plans/assets/2026-08-03-poll-results-page.html @@ -516,9 +516,9 @@ .app-btn:focus-visible { outline: 2px solid var(--app-accent); outline-offset: 2px; } - /* Voter list. Row anatomy is UserCompose's, not a new one: SlimListItem with - UserPicture / UsernameDisplay / AboutDisplay, and the vote in the trailing - slot where the follow buttons normally sit. */ + /* Voter list. This is UserLine, the app's existing user row: SlimListItem with + ClickableUserPicture / UsernameDisplay / WatchAndDisplayNip05Row, and the + vote passed into the trailingContent slot it already exposes. */ .voters { display: flex; flex-direction: column; } .voter { display: flex; @@ -536,10 +536,42 @@ white-space: nowrap; text-overflow: ellipsis; } - /* AboutDisplay: profile about text, one line, ellipsized, placeholderText - (= onSurface at 42%). */ - .voter-about { - font-size: 12.5px; + /* WatchAndDisplayNip05Row: local part · verified symbol · domain, no "@", + all in colorScheme.nip05 (= the accent). Falls back to the npub only when + the user has no NIP-05 at all. */ + .voter-nip05 { + display: flex; + align-items: center; + gap: 3px; + min-width: 0; + font-size: 13px; + color: var(--app-accent); + } + /* Only the local part ellipsizes; the domain is TextOverflow.Visible in the + app, so it never truncates. */ + .n5-local { + flex: 0 1 auto; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .n5-domain { flex: none; white-space: nowrap; } + .n5-check { + flex: none; + width: 13px; + height: 13px; + border-radius: 50%; + background: var(--app-accent); + color: var(--app-bg); + font-size: 9px; + font-weight: 700; + display: grid; + place-items: center; + line-height: 1; + } + .voter-npub { + font-size: 13px; color: var(--app-placeholder); overflow: hidden; white-space: nowrap; @@ -1224,7 +1256,7 @@
VP
you
-
Android + KMP. Nostr all day.
+
me✓nostrplebs.com
nos.lol
2d ago
@@ -1232,7 +1264,7 @@
BS
Bruno Sá
-
Relay operator in Lisbon. Runs a small paid relay.
+
bruno✓nostrplebs.com
relay.damus.io
2d ago
@@ -1240,7 +1272,7 @@
MK
mira.k
-
Designer. Making nostr less frightening.
+
mira✓nostrcheck.me
nos.lol
2d ago
@@ -1248,7 +1280,7 @@
AR
Ana Reis
-
Photographer, mostly film.
+
ana✓nostrcheck.me
nos.lol
1d ago
@@ -1256,14 +1288,15 @@
RW
relaywrangler
-
Self-hosting everything since 2011.
+
wrangler✓relay.tools
purplepag.es
1d ago
-
+
KM
-
npub1q8f…j3xw
+
Katya M.
+
npub1q8f…j3xw
relay.damus.io
22h ago
@@ -1271,7 +1304,7 @@
TS
tomás
-
café, código, bicicleta
+
tomas✓nostr.pt
nos.lol
14h ago
@@ -1279,7 +1312,7 @@
HM
hodlmatrix
-
21M or bust.
+
✓21m.io
purplepag.es
9h ago
@@ -1352,14 +1385,14 @@
6
-
A voter row is just a user row
+
This is UserLine, unmodified

- Not a new row type — SlimListItem with exactly the slots - UserCompose fills: UserPicture, - UsernameDisplay, AboutDisplay. Only the trailing slot differs, - carrying the vote where the follow buttons normally sit. So the second line is the - person's about text, petnames and display-name emoji work, and the npub fallback - (row six) appears on its own when no profile has arrived — all for free. + Not a new row, and not an edited one: the results screen calls the app's existing + UserLine and passes the vote as its + trailingContent, a parameter it already has. So the second line is the + NIP-05 identifier the way the app draws it — local part, verified mark, domain, + no @ — with a root identifier showing the domain alone (row eight) and the + npub appearing only where there is no NIP-05 to show (row six).

@@ -1678,7 +1711,7 @@
VP
you
-
Android + KMP. Nostr all day.
+
me✓nostrplebs.com
nos.lol
2d ago
@@ -1686,7 +1719,7 @@
BS
Bruno Sá
-
Relay operator in Lisbon.
+
bruno✓nostrplebs.com
relay.damus.io
2d ago
@@ -1694,7 +1727,7 @@
MK
mira.k
-
Designer. Making nostr less frightening.
+
mira✓nostrcheck.me
nos.lol
2d ago
@@ -1787,7 +1820,7 @@
P2
-
The screenViewModel + shared composables + the UserCompose trailing slot + Android route + entry points from the card.
+
The screenViewModel + shared composables + Android route + entry points from the card. No existing composable is modified.
The feature