From 2d6221fd10d1a60f26edc73bc78c276f78a434cd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 18:34:53 +0000 Subject: [PATCH] feat: show curated descriptions for discover web apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render the browser "Discover" section as full-width rows (icon + name + one-line description) instead of bare icon cells, matching the Recent row layout which already carries a subtitle. Each suggestion now has a short curated description (trimmed from the app's own meta description) so unfamiliar apps explain themselves; tapping a row opens the app, and a trailing star pins it to favorites. Auto-pulling page /description was rejected: many of these apps are client-rendered SPAs that serve an empty <title>, and several titles are long marketing strings — curated short names read better in the list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0151Uczec41LhTogxkgoAhKa --- .../screen/loggedIn/browser/BrowserScreen.kt | 62 ++++++- .../loggedIn/favorites/FavoriteAppsScreen.kt | 51 ++---- .../commons/browser/DefaultWebClients.kt | 166 ++++++++++-------- 3 files changed, 158 insertions(+), 121 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt index 666fea3b81..eea38b3ca2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt @@ -74,7 +74,9 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.browser.DefaultWebClients import com.vitorpamplona.amethyst.commons.browser.OmniboxInput import com.vitorpamplona.amethyst.commons.browser.OmniboxSuggestions +import com.vitorpamplona.amethyst.commons.browser.SuggestedWebApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp +import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter @@ -90,7 +92,6 @@ import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.favoriteAppItems -import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.suggestedAppItems import com.vitorpamplona.amethyst.commons.R as CommonsR /** How many of the most recent history entries the idle browser home surfaces under "Recent". */ @@ -214,7 +215,7 @@ private fun BrowserLauncher( else -> { val favoriteUrls = remember(apps) { apps.filterIsInstance<FavoriteApp.WebApp>().mapTo(HashSet()) { it.url } } // Hardcoded starter web apps, minus any the user already pinned (those show under Favorites). - val suggested = remember(favoriteUrls) { DefaultWebClients.list.filter { it.url !in favoriteUrls } } + val suggested = remember(favoriteUrls) { DefaultWebClients.list.filter { it.app.url !in favoriteUrls } } BrowserHome( apps = apps, history = history, @@ -369,7 +370,7 @@ private fun BrowserHome( history: List<BrowserHistoryEntry>, iconKeys: Set<String>, favoriteUrls: Set<String>, - suggested: List<FavoriteApp.WebApp>, + suggested: List<SuggestedWebApp>, onOpenApp: (FavoriteApp) -> Unit, onRemoveApp: (FavoriteApp) -> Unit, onAddApp: (FavoriteApp) -> Unit, @@ -405,7 +406,60 @@ private fun BrowserHome( } if (suggested.isNotEmpty()) { item(span = { GridItemSpan(maxLineSpan) }, key = "h-sug") { SectionHeader(stringResource(R.string.browser_suggested)) } - suggestedAppItems(suggested, onOpenApp, onAddApp) + items(suggested, span = { GridItemSpan(maxLineSpan) }, key = { "s:" + it.app.url }) { entry -> + SuggestedRow( + entry = entry, + iconKeys = iconKeys, + onClick = { onOpenApp(entry.app) }, + onAddFavorite = { onAddApp(entry.app) }, + ) + } + } + } +} + +/** A Discover row: the app's own icon, its name, and a one-line description, with a star to pin it. */ +@Composable +private fun SuggestedRow( + entry: SuggestedWebApp, + iconKeys: Set<String>, + onClick: () -> Unit, + onAddFavorite: () -> Unit, +) { + val iconModel = remember(entry, iconKeys) { OmniboxInput.hostOf(entry.app.url)?.let(BrowserIconRegistry::iconModelFor) } + Row( + modifier = + Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .clickable(onClick = onClick) + .padding(start = 8.dp, top = 4.dp, bottom = 4.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + FavoriteAppIcon( + app = entry.app, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(28.dp), + iconModel = iconModel, + ) + Spacer(Modifier.width(16.dp)) + Column(Modifier.weight(1f)) { + Text( + entry.app.label, + style = MaterialTheme.typography.bodyLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + entry.description, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } + IconButton(onClick = onAddFavorite) { + Icon(MaterialSymbols.StarBorder, contentDescription = stringResource(R.string.favorite_app_add)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt index 90eda7dbe3..1fc3bbac78 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt @@ -167,45 +167,7 @@ fun LazyGridScope.favoriteAppItems( FavoriteAppCell( app = app, onOpen = { onOpen(app) }, - menu = { dismiss -> - DropdownMenuItem( - text = { Text(stringResource(R.string.favorite_app_remove)) }, - leadingIcon = { Icon(MaterialSymbols.Delete, contentDescription = null) }, - onClick = { - dismiss() - onRemove(app) - }, - ) - }, - ) - } -} - -/** - * Emits the same launch cells for a list of *suggested* web apps (the hardcoded defaults the browser - * home offers under "Discover web apps"). Identical to [favoriteAppItems] except the long-press menu offers - * "Add to favorites" instead of "Remove" — these aren't pinned yet, so tapping star is what the user - * would want next. - */ -fun LazyGridScope.suggestedAppItems( - apps: List<FavoriteApp>, - onOpen: (FavoriteApp) -> Unit, - onAddFavorite: (FavoriteApp) -> Unit, -) { - items(apps, key = { "suggested:" + it.id }) { app -> - FavoriteAppCell( - app = app, - onOpen = { onOpen(app) }, - menu = { dismiss -> - DropdownMenuItem( - text = { Text(stringResource(R.string.favorite_app_add)) }, - leadingIcon = { Icon(MaterialSymbols.StarBorder, contentDescription = null) }, - onClick = { - dismiss() - onAddFavorite(app) - }, - ) - }, + onRemove = { onRemove(app) }, ) } } @@ -215,7 +177,7 @@ fun LazyGridScope.suggestedAppItems( internal fun FavoriteAppCell( app: FavoriteApp, onOpen: () -> Unit, - menu: @Composable (dismiss: () -> Unit) -> Unit, + onRemove: () -> Unit, ) { var menuOpen by remember { mutableStateOf(false) } @@ -268,7 +230,14 @@ internal fun FavoriteAppCell( ) DropdownMenu(expanded = menuOpen, onDismissRequest = { menuOpen = false }) { - menu { menuOpen = false } + DropdownMenuItem( + text = { Text(stringResource(R.string.favorite_app_remove)) }, + leadingIcon = { Icon(MaterialSymbols.Delete, contentDescription = null) }, + onClick = { + menuOpen = false + onRemove() + }, + ) } } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/browser/DefaultWebClients.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/browser/DefaultWebClients.kt index b2459d69cb..c80acc53ae 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/browser/DefaultWebClients.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/browser/DefaultWebClients.kt @@ -20,117 +20,131 @@ */ package com.vitorpamplona.amethyst.commons.browser +import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp +/** One suggested web app: the launchable [app] plus a short [description] of what it does. */ +@Immutable +data class SuggestedWebApp( + val app: FavoriteApp.WebApp, + val description: String, +) + /** * The Nostr **web apps** offered as starting points in the browser launcher when the user hasn't * pinned or visited anything of their own yet. They are shown under a "Discover" section below Recent, * so they're discoverable without ever getting in the way of the user's own favorites/history. * - * The list is drawn from the [nostrapps.com](https://nostrapps.com) directory — every entry that ships - * a browser-openable web version — grouped here by what it does. Entries that are browser *extensions* - * or signer-only tools (nos2x, Nostrame, …) are intentionally left out: they aren't something you open - * in an in-app browser. URLs are the apps' own canonical domains. + * The list is drawn from the [nostrapps.com](https://nostrapps.com) directory (cross-checked against + * [awesome-nostr](https://github.com/aljazceru/awesome-nostr)) — every entry that ships a + * browser-openable web version — grouped here by what it does. Entries that are browser *extensions* or + * signer-only tools (nos2x, Nostrame, …) are intentionally left out: they aren't something you open in + * an in-app browser. URLs are the apps' own canonical domains. * - * Each [icon] is the app's **own** logo (the PNG/SVG declared in its `<link rel="apple-touch-icon">` / - * `<link rel="icon">`), so the grid matches the favicon look of the Favorites/Recent rows without - * routing through any third-party favicon service. Only PNG/SVG are used — Coil has no ICO decoder, so - * an apps whose only icon is a `.ico` (and a few that couldn't be resolved) are left [icon]-less and - * fall back to the globe glyph until their real favicon is captured the normal way on first visit. + * Each entry carries: + * - a short curated [SuggestedWebApp.description] (a trimmed version of the app's own meta description) + * shown as the row subtitle, since these are apps the user likely hasn't seen before; + * - the app's **own** logo as [FavoriteApp.iconUrl] (the PNG/SVG declared in its + * `<link rel="apple-touch-icon">` / `<link rel="icon">`), so it matches the favicon look of the + * Favorites/Recent rows without routing through any third-party favicon service. Only PNG/SVG are + * used — Coil has no ICO decoder — so apps whose only icon is a `.ico` (and a few that couldn't be + * resolved) are left icon-less and fall back to the globe glyph until their real favicon is captured + * the normal way on first visit. * - * These are plain [FavoriteApp.WebApp] entries, so tapping one opens it like any other web favorite and - * the user can star it to pin it for real. They are **not** persisted as favorites: the list is - * hardcoded, device-local, and never becomes account state. + * The [app]s are plain [FavoriteApp.WebApp] entries, so tapping one opens it like any other web + * favorite and the user can star it to pin it for real. They are **not** persisted as favorites: the + * list is hardcoded, device-local, and never becomes account state. */ object DefaultWebClients { - val list: List<FavoriteApp.WebApp> = + val list: List<SuggestedWebApp> = buildList { // Social / microblogging clients - webApp("Primal", "https://primal.net", "https://primal.net/assets/apple-touch-icon-a536f430.png") - webApp("Coracle", "https://coracle.social", "https://coracle.social/icons/apple-touch-icon-76x76.png") - webApp("Snort", "https://snort.social", "https://snort.social/img/apple-touch-icon.png") - webApp("noStrudel", "https://nostrudel.ninja", "https://nostrudel.ninja/apple-touch-icon.png") - webApp("Iris", "https://iris.to", "https://iris.to/img/apple-touch-icon.png") - webApp("Nostter", "https://nostter.app", "https://nostter.app/apple-touch-icon.png") - webApp("Jumble", "https://jumble.social", "https://jumble.social/favicon.svg") - webApp("Nostria", "https://nostria.app", "https://nostria.app/icons/icon-192x192-maskable.png") - webApp("Nosotros", "https://nosotros.app", "https://nosotros.app/apple-touch-icon-144x144.png") - webApp("lumilumi", "https://lumilumi.app", "https://lumilumi.app/apple-touch-icon-180x180.png") - webApp("Phoenix", "https://phoenix.social", "https://phoenix.social/img/apple-touch-icon.png") - webApp("Shosho", "https://shosho.live", "https://shosho.live/apple-touch-icon.png") - webApp("ants", "https://ants.sh", "https://ants.sh/apple-touch-icon.png") - webApp("YakiHonne", "https://yakihonne.com") - webApp("Ditto", "https://ditto.pub", "https://ditto.pub/apple-touch-icon.png") - webApp("x21", "https://x21.social", "https://x21.social/apple-touch-icon.png?v=4") - webApp("Ghostr", "https://ghostr.org", "https://ghostr.org/favicon/apple-touch-icon.png") - webApp("Mutable", "https://mutable.top", "https://mutable.top/mutable_logo.svg") + webApp("Primal", "https://primal.net", "All-in-one client with a built-in wallet", "https://primal.net/assets/apple-touch-icon-a536f430.png") + webApp("Coracle", "https://coracle.social", "Relay-savvy client for regular people", "https://coracle.social/icons/apple-touch-icon-76x76.png") + webApp("Snort", "https://snort.social", "Fast, feature-packed social client", "https://snort.social/img/apple-touch-icon.png") + webApp("noStrudel", "https://nostrudel.ninja", "Power-user client for exploring Nostr", "https://nostrudel.ninja/apple-touch-icon.png") + webApp("Iris", "https://iris.to", "Simple, fast social client", "https://iris.to/img/apple-touch-icon.png") + webApp("Nostter", "https://nostter.app", "Lightweight web social client", "https://nostter.app/apple-touch-icon.png") + webApp("Jumble", "https://jumble.social", "Explore feeds relay by relay", "https://jumble.social/favicon.svg") + webApp("Nostria", "https://nostria.app", "Social without the noise", "https://nostria.app/icons/icon-192x192-maskable.png") + webApp("Nosotros", "https://nosotros.app", "A weirdly fast social client", "https://nosotros.app/apple-touch-icon-144x144.png") + webApp("lumilumi", "https://lumilumi.app", "Lightweight Nostr client", "https://lumilumi.app/apple-touch-icon-180x180.png") + webApp("Phoenix", "https://phoenix.social", "Snort-based social client", "https://phoenix.social/img/apple-touch-icon.png") + webApp("Shosho", "https://shosho.live", "Live-streaming marketplace", "https://shosho.live/apple-touch-icon.png") + webApp("ants", "https://ants.sh", "Advanced Nostr text search", "https://ants.sh/apple-touch-icon.png") + webApp("YakiHonne", "https://yakihonne.com", "Decentralized media & long-form") + webApp("Ditto", "https://ditto.pub", "Your content, your vibe, your rules", "https://ditto.pub/apple-touch-icon.png") + webApp("x21", "https://x21.social", "Relay feed explorer", "https://x21.social/apple-touch-icon.png?v=4") + webApp("Ghostr", "https://ghostr.org", "Draft & delegated publishing", "https://ghostr.org/favicon/apple-touch-icon.png") + webApp("Mutable", "https://mutable.top", "Your mute list manager", "https://mutable.top/mutable_logo.svg") // Reading / long-form / feeds - webApp("Habla", "https://habla.news") - webApp("Highlighter", "https://highlighter.com", "https://highlighter.com/apple-touch-icon-180x180.png") - webApp("Boris", "https://readwithboris.com", "https://readwithboris.com/apple-touch-icon.png") - webApp("Noflux", "https://noflux.nostr.technology") + webApp("Habla", "https://habla.news", "Long-form articles & blogs") + webApp("Highlighter", "https://highlighter.com", "Articles, highlights & communities", "https://highlighter.com/apple-touch-icon-180x180.png") + webApp("Boris", "https://readwithboris.com", "Distraction-free reading & highlights", "https://readwithboris.com/apple-touch-icon.png") + webApp("Noflux", "https://noflux.nostr.technology", "RSS-style feed reader") // Communities / chat - webApp("Flotilla", "https://flotilla.social", "https://framerusercontent.com/images/8UjnVxSvRkmvY2lEYU5z8OMOw0M.png") - webApp("Chachi", "https://chachi.chat") - webApp("NostrChat", "https://www.nostrchat.io", "https://www.nostrchat.io/logo192.png") - webApp("NymChat", "https://www.nymchat.com") + webApp("Flotilla", "https://flotilla.social", "Community spaces & chat", "https://framerusercontent.com/images/8UjnVxSvRkmvY2lEYU5z8OMOw0M.png") + webApp("Chachi", "https://chachi.chat", "Group chat & communities") + webApp("NostrChat", "https://www.nostrchat.io", "Decentralized chat", "https://www.nostrchat.io/logo192.png") + webApp("NymChat", "https://www.nymchat.com", "Anonymous, ephemeral chat") // Media — video, photo, audio, podcasts, files - webApp("zap.stream", "https://zap.stream", "https://zap.stream/logo.png") - webApp("Divine Video", "https://divine.video", "https://divine.video/app_icon.png") - webApp("Olas", "https://olas.app", "https://olas.app/favicon.png") - webApp("Zappix", "https://zappix.app", "https://zappix.app/icon-192.png") - webApp("Slidestr", "https://slidestr.net", "https://slidestr.net/slidestr.svg") - webApp("Bouquet", "https://bouquet.slidestr.net", "https://bouquet.slidestr.net/bouquet.png") - webApp("YakBak", "https://yakbak.app", "https://yakbak.app/yakbak-logo.png") - webApp("ZapTrax", "https://zaptrax.app", "https://zaptrax.app/icon-192.png") - webApp("Podstr", "https://podstr.org", "https://podstr.org/favicon.svg") - webApp("Nests", "https://nostrnests.com", "https://nostrnests.com/apple-touch-icon.png") + webApp("zap.stream", "https://zap.stream", "Live streaming with Lightning", "https://zap.stream/logo.png") + webApp("Divine Video", "https://divine.video", "6-second looping videos", "https://divine.video/app_icon.png") + webApp("Olas", "https://olas.app", "Photo & media sharing", "https://olas.app/favicon.png") + webApp("Zappix", "https://zappix.app", "Share & discover images", "https://zappix.app/icon-192.png") + webApp("Slidestr", "https://slidestr.net", "Media slideshow viewer", "https://slidestr.net/slidestr.svg") + webApp("Bouquet", "https://bouquet.slidestr.net", "Blossom media manager", "https://bouquet.slidestr.net/bouquet.png") + webApp("YakBak", "https://yakbak.app", "Voice messages", "https://yakbak.app/yakbak-logo.png") + webApp("ZapTrax", "https://zaptrax.app", "Music streaming with Wavlake", "https://zaptrax.app/icon-192.png") + webApp("Podstr", "https://podstr.org", "Podcasts on Nostr", "https://podstr.org/favicon.svg") + webApp("Nests", "https://nostrnests.com", "Live audio rooms", "https://nostrnests.com/apple-touch-icon.png") // Knowledge / wiki - webApp("Wikifreedia", "https://wikifreedia.xyz", "https://wikifreedia.xyz/favicon.svg") - webApp("Wikistr", "https://wikistr.com", "https://wikistr.com/favicon.png") + webApp("Wikifreedia", "https://wikifreedia.xyz", "Decentralized encyclopedia", "https://wikifreedia.xyz/favicon.svg") + webApp("Wikistr", "https://wikistr.com", "A wiki built on Nostr", "https://wikistr.com/favicon.png") // Marketplace / food - webApp("Shopstr", "https://shopstr.store") - webApp("Plebeian Market", "https://plebeian.market", "https://plebeian.market/logo-st5zpap9.svg") - webApp("Zap Cooking", "https://zap.cooking", "https://zap.cooking/favicon.svg") + webApp("Shopstr", "https://shopstr.store", "Bitcoin-native marketplace") + webApp("Plebeian Market", "https://plebeian.market", "Decentralized marketplace", "https://plebeian.market/logo-st5zpap9.svg") + webApp("Zap Cooking", "https://zap.cooking", "Recipes & food culture", "https://zap.cooking/favicon.svg") // Tools / utilities - webApp("Emojito", "https://emojito.meme") - webApp("Formstr", "https://formstr.app", "https://formstr.app/logo192.png") - webApp("Nostree", "https://nostree.me") - webApp("Badges", "https://badges.page") - webApp("Nstart", "https://nstart.me", "https://nstart.me/favicon.png") - webApp("alphaama", "https://alphaama.com") - webApp("Treasures", "https://treasures.to", "https://treasures.to/apple-touch-icon.png") - webApp("Yondar", "https://yondar.me", "https://yondar.me/apple-touch-icon.png") - webApp("DTAN", "https://dtan.xyz") - webApp("Nostrocket", "https://nostrocket.org") - webApp("Plektos", "https://plektos.app", "https://plektos.app/icon-180.png") - webApp("Zaplytics", "https://zaplytics.app") - webApp("Brainstorm", "https://brainstorm.world", "https://brainstorm.world/brainstorm.svg") - webApp("MAKIMONO", "https://makimono.lumilumi.app", "https://makimono.lumilumi.app/favicon3.png") - webApp("Primal Studio", "https://studio.primal.net") - webApp("nostr.build", "https://nostr.build", "https://nostr.build/apple-touch-icon.png") - webApp("nostrcheck", "https://nostrcheck.me", "https://nostrcheck.me/apple-touch-icon.png") - webApp("Metadata", "https://metadata.nostr.com") + webApp("Emojito", "https://emojito.meme", "Custom emoji sets") + webApp("Formstr", "https://formstr.app", "Decentralized forms", "https://formstr.app/logo192.png") + webApp("Nostree", "https://nostree.me", "Link-in-bio pages") + webApp("Badges", "https://badges.page", "Create & award badges") + webApp("Nstart", "https://nstart.me", "Guided account onboarding", "https://nstart.me/favicon.png") + webApp("alphaama", "https://alphaama.com", "Nostr tools & experiments") + webApp("Treasures", "https://treasures.to", "Geocaching on Nostr", "https://treasures.to/apple-touch-icon.png") + webApp("Yondar", "https://yondar.me", "Places & maps", "https://yondar.me/apple-touch-icon.png") + webApp("DTAN", "https://dtan.xyz", "Torrents on Nostr") + webApp("Nostrocket", "https://nostrocket.org", "Project coordination") + webApp("Plektos", "https://plektos.app", "Decentralized meetup events", "https://plektos.app/icon-180.png") + webApp("Zaplytics", "https://zaplytics.app", "Zap analytics for creators") + webApp("Brainstorm", "https://brainstorm.world", "Web-of-trust explorer", "https://brainstorm.world/brainstorm.svg") + webApp("MAKIMONO", "https://makimono.lumilumi.app", "Long-form article editor", "https://makimono.lumilumi.app/favicon3.png") + webApp("Primal Studio", "https://studio.primal.net", "Schedule & publish content") + webApp("nostr.build", "https://nostr.build", "Media & image uploads", "https://nostr.build/apple-touch-icon.png") + webApp("nostrcheck", "https://nostrcheck.me", "Media hosting & NIP-05", "https://nostrcheck.me/apple-touch-icon.png") + webApp("Metadata", "https://metadata.nostr.com", "Edit your profile metadata") // Games - webApp("Plebs vs Zombies", "https://www.plebsvszombies.cc", "https://www.plebsvszombies.cc/favicon.svg") - webApp("Blobbi", "https://www.blobbi.pet", "https://www.blobbi.pet/icons/apple-touch-icon.png") + webApp("Plebs vs Zombies", "https://www.plebsvszombies.cc", "Clean up your follow list", "https://www.plebsvszombies.cc/favicon.svg") + webApp("Blobbi", "https://www.blobbi.pet", "A virtual pet game", "https://www.blobbi.pet/icons/apple-touch-icon.png") } // addedAt is 0L: these are hardcoded suggestions, not user-added favorites, so they never need a // real "added" timestamp for ordering — the curated order in `list` is what matters. `icon` is the // app's own logo URL, or null to fall back to the globe glyph until a favicon is captured on visit. - private fun MutableList<FavoriteApp.WebApp>.webApp( + private fun MutableList<SuggestedWebApp>.webApp( label: String, url: String, + description: String, icon: String? = null, ) { - add(FavoriteApp.WebApp(url = url, label = label, addedAt = 0L, iconUrl = icon)) + add(SuggestedWebApp(FavoriteApp.WebApp(url = url, label = label, addedAt = 0L, iconUrl = icon), description)) } }