From 0b887d62f61fb88458ea904977755da4c546440d Mon Sep 17 00:00:00 2001 From: triesap Date: Tue, 17 Feb 2026 19:04:38 +0000 Subject: [PATCH 1/2] Move page title logic to util --- src/app/util/title.ts | 154 ++++++++++++++++++++++++++++++++++++++ src/routes/+layout.svelte | 12 +++ 2 files changed, 166 insertions(+) create mode 100644 src/app/util/title.ts diff --git a/src/app/util/title.ts b/src/app/util/title.ts new file mode 100644 index 00000000..f69ee091 --- /dev/null +++ b/src/app/util/title.ts @@ -0,0 +1,154 @@ +import {append, identity, uniq} from "@welshman/lib" +import {displayPubkey, getTagValue, type Filter, type TrustedEvent} from "@welshman/util" +import { + PLATFORM_NAME, + decodeRelay, + getEventsForUrl, + getRoom, + makeRoomId, + splitChatId, +} from "@app/core/state" + +const FALLBACK_APP_NAME = "Flotilla" + +const staticTitles = new Map([ + ["/", "Redirecting"], + ["/home", "Home"], + ["/discover", "Discover Spaces"], + ["/spaces", "Your Spaces"], + ["/spaces/create", "Create a Space"], + ["/spaces/[relay]", "Space"], + ["/spaces/[relay]/chat", "Space Chat"], + ["/spaces/[relay]/recent", "Recent Activity"], + ["/spaces/[relay]/threads", "Threads"], + ["/spaces/[relay]/classifieds", "Classifieds"], + ["/spaces/[relay]/calendar", "Calendar"], + ["/spaces/[relay]/goals", "Goals"], + ["/chat", "Messages"], + ["/join", "Join Space"], + ["/people", "Find People"], + ["/settings/about", "About"], + ["/settings/profile", "Profile Settings"], + ["/settings/content", "Content Settings"], + ["/settings/privacy", "Privacy Settings"], + ["/settings/relays", "Relay Settings"], + ["/settings/alerts", "Alert Settings"], + ["/settings/wallet", "Wallet Settings"], + ["/[bech32]", "Opening Link"], +]) + +const eventRoutes = new Set([ + "/spaces/[relay]/threads/[id]", + "/spaces/[relay]/goals/[id]", + "/spaces/[relay]/calendar/[address]", + "/spaces/[relay]/classifieds/[address]", +]) + +type RouteParams = Record + +type TitlePage = { + route: {id: string | null} + params: RouteParams +} + +type PageTitleContext = { + page: TitlePage + pubkey: string | undefined +} + +const getRoomTitle = (params: RouteParams) => { + const relay = params.relay + const h = params.h + + if (!relay || !h) { + return "Room" + } + + const url = decodeRelay(relay) + + return getRoom(makeRoomId(url, h))?.name || "Room" +} + +const getEventForTitle = (routeId: string, params: RouteParams): TrustedEvent | undefined => { + const relay = params.relay + + if (!relay || !eventRoutes.has(routeId)) { + return + } + + const eventId = params.id || params.address + + if (!eventId) { + return + } + + const url = decodeRelay(relay) + const filters: Filter[] = [{ids: [eventId], limit: 1}] + const events = Array.from(getEventsForUrl(url, filters)) + + return events[0] +} + +const getChatTitle = (chatId: string | undefined, pubkey: string | undefined) => { + if (!chatId) { + return "Chat" + } + + const chatPeers = pubkey ? uniq(append(pubkey, splitChatId(chatId))) : splitChatId(chatId) + const others = pubkey ? chatPeers.filter(pk => pk !== pubkey) : chatPeers + + if (others.length === 1) { + return `Chat with ${displayPubkey(others[0])}` + } + + if (others.length > 1) { + return `Group chat (${others.length})` + } + + return "Chat" +} + +export const makeTitle = (...parts: Array) => + parts + .map(part => part?.trim() || "") + .filter(identity) + .join(" ยท ") || + PLATFORM_NAME || + FALLBACK_APP_NAME + +export const getPageTitle = ({page, pubkey}: PageTitleContext) => { + const routeId = page.route.id || "" + const staticTitle = staticTitles.get(routeId) + + if (staticTitle) { + return makeTitle(staticTitle) + } + + if (routeId === "/chat/[chat]") { + return makeTitle(getChatTitle(page.params.chat, pubkey)) + } + + if (routeId === "/spaces/[relay]/[h]") { + return makeTitle(getRoomTitle(page.params)) + } + + const event = getEventForTitle(routeId, page.params) + + if (routeId === "/spaces/[relay]/threads/[id]") { + return makeTitle(getTagValue("title", event?.tags || []) || "Thread") + } + + if (routeId === "/spaces/[relay]/calendar/[address]") { + return makeTitle(getTagValue("title", event?.tags || []) || "Event") + } + + if (routeId === "/spaces/[relay]/classifieds/[address]") { + return makeTitle(getTagValue("title", event?.tags || []) || "Listing") + } + + if (routeId === "/spaces/[relay]/goals/[id]") { + return makeTitle(event?.content || getTagValue("summary", event?.tags || []) || "Goal") + } + + return makeTitle() +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e23f0015..79b20506 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -7,6 +7,7 @@ import {App, type URLOpenListenerEvent} from "@capacitor/app" import {dev} from "$app/environment" import {goto} from "$app/navigation" + import {page} from "$app/stores" import {sync, throttled} from "@welshman/store" import {call} from "@welshman/lib" import {defaultSocketPolicies} from "@welshman/net" @@ -42,10 +43,15 @@ import * as notifications from "@app/util/notifications" import * as storage from "@app/util/storage" import {syncKeyboard} from "@app/util/keyboard" + import {getPageTitle} from "@app/util/title" import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte" const {children} = $props() + const pageTitle = $derived.by(() => { + return getPageTitle({page: $page, pubkey: $pubkey}) + }) + const policies = [authPolicy, blockPolicy, trustPolicy, mostlyRestrictedPolicy] // Add stuff to window for convenience @@ -199,6 +205,12 @@ App.removeAllListeners() unsubscribe.then(call) }) + + $effect(() => { + if (typeof document !== "undefined") { + document.title = pageTitle + } + }) -- 2.52.0 From 275eb46565549037a2d052a7cc569e3dc9cacccb Mon Sep 17 00:00:00 2001 From: triesap Date: Tue, 17 Feb 2026 20:19:14 +0000 Subject: [PATCH 2/2] Simplify page title logic per review --- AGENTS.md | 9 +++++++++ src/app/util/title.ts | 24 ++++++------------------ src/routes/+layout.svelte | 8 +------- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f1aa7e3b..0ff74656 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -170,6 +170,15 @@ src/ - Do not define svelte event handlers inline, instead name them and put them in the script section of templates - Avoid using `as`, except where necessary. Instead, annotate function parameters, and ensure upstream values are typed correctly. +**Human-First Simplicity (Jon Staab Style):** + +- Prefer direct, readable code over layered abstractions. +- Do not add indirection (extra helpers, wrappers, stores, or derived state) unless it removes real repeated complexity. +- Reuse existing Welshman and Flotilla primitives before introducing new utilities or dependencies. +- Favor linear control flow and explicit naming over clever patterns. +- Remove defensive checks that do not apply in this runtime model. +- When two approaches work, pick the one that feels more human and easier to maintain. + ## Common Tasks ### Adding a New Component diff --git a/src/app/util/title.ts b/src/app/util/title.ts index f69ee091..b39864eb 100644 --- a/src/app/util/title.ts +++ b/src/app/util/title.ts @@ -1,13 +1,7 @@ import {append, identity, uniq} from "@welshman/lib" -import {displayPubkey, getTagValue, type Filter, type TrustedEvent} from "@welshman/util" -import { - PLATFORM_NAME, - decodeRelay, - getEventsForUrl, - getRoom, - makeRoomId, - splitChatId, -} from "@app/core/state" +import {repository} from "@welshman/app" +import {displayPubkey, getTagValue} from "@welshman/util" +import {PLATFORM_NAME, decodeRelay, getRoom, makeRoomId, splitChatId} from "@app/core/state" const FALLBACK_APP_NAME = "Flotilla" @@ -69,10 +63,8 @@ const getRoomTitle = (params: RouteParams) => { return getRoom(makeRoomId(url, h))?.name || "Room" } -const getEventForTitle = (routeId: string, params: RouteParams): TrustedEvent | undefined => { - const relay = params.relay - - if (!relay || !eventRoutes.has(routeId)) { +const getEventForTitle = (routeId: string, params: RouteParams) => { + if (!eventRoutes.has(routeId)) { return } @@ -82,11 +74,7 @@ const getEventForTitle = (routeId: string, params: RouteParams): TrustedEvent | return } - const url = decodeRelay(relay) - const filters: Filter[] = [{ids: [eventId], limit: 1}] - const events = Array.from(getEventsForUrl(url, filters)) - - return events[0] + return repository.getEvent(eventId) } const getChatTitle = (chatId: string | undefined, pubkey: string | undefined) => { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 79b20506..9cd3be65 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -48,10 +48,6 @@ const {children} = $props() - const pageTitle = $derived.by(() => { - return getPageTitle({page: $page, pubkey: $pubkey}) - }) - const policies = [authPolicy, blockPolicy, trustPolicy, mostlyRestrictedPolicy] // Add stuff to window for convenience @@ -207,9 +203,7 @@ }) $effect(() => { - if (typeof document !== "undefined") { - document.title = pageTitle - } + document.title = getPageTitle({page: $page, pubkey: $pubkey}) }) -- 2.52.0