Replace the Material 3 DockedSearchBar with a persistent, pill-shaped
search field that filters the settings list in place instead of opening
a results dropdown. The docked component is aimed at a search surface
with its own results view (and the tablet/desktop form factor); an
in-page filter — as in the Android system Settings app — is a better fit
here.
- Rounded, tonal search field pinned above the list (Material 3 look).
- Typing narrows the categorized list directly; a blank query shows all.
- Clear (X) resets the query; "no results" state when nothing matches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQYnKXff1esD1bdPGNzyHp
Switch the Settings search from a custom expand-into-the-top-bar icon to
the Material 3 DockedSearchBar component, matching the modern in-page
search convention (e.g. Android Settings).
- A persistent search pill sits below the "Settings" title bar.
- Tapping it expands the docked results dropdown, listing the filtered
settings (a blank query lists everything, narrowing as the user types).
- The back arrow, the system back gesture, and picking a result all
collapse the bar and clear the query.
- The full categorized settings list shows below the pill while collapsed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQYnKXff1esD1bdPGNzyHp
The Settings screen previously showed a permanent search text field below
the top bar. Replace it with a search icon in the top bar's actions slot
that expands into an inline text field when pressed.
- Collapsed: shows the "Settings" title plus a search icon action.
- Expanded: the title becomes an auto-focused inline search field, the
back arrow (and system back) collapses it and clears the query, and a
clear (X) action appears while there is text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PQYnKXff1esD1bdPGNzyHp
With the Gradle distribution now pre-seeded in the web sandbox
(session-start.sh) and dependencies resolving through the proxy, spotlessApply
no longer fails for infra reasons. Remove the network-error skip branch so any
spotlessApply failure blocks the push: a failure now means a real
formatting/compile error, and the narrow regex can no longer mask one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7bocRTSPyXwz6zVMzDbKR
Claude Code Web routes github.com through a git-only proxy, so the Gradle
wrapper's distributionUrl (services.gradle.org, which 307-redirects to a
github.com release asset) can't bootstrap — it 403s even at Full network
access, and `./gradlew` fails before running any task.
Seed the pinned distribution in the existing web-only SessionStart hook,
reusing the same idempotent curl-download pattern already used there for the
Android SDK and Kotlin/Native deps: skip if already installed, else fetch
Gradle's OFFICIAL sha256 (served from services.gradle.org, reachable here),
download the zip from a mirror, and verify before extracting so a tampered or
wrong mirror file is rejected and never executed. The wrapper cache dir is
derived as base36(md5(distributionUrl)) so it survives version bumps.
Also pin distributionSha256Sum in gradle-wrapper.properties as defense in
depth: Gradle then verifies any distribution it installs (mirror-seeded, CI,
or local) against the known-good hash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A7bocRTSPyXwz6zVMzDbKR
Correctness:
- GeoRelayDirectory.relays is now @Volatile; on the process-wide `shared`
directory the CSV refresh was written by one thread and read by others with
no memory barrier, so readers could keep using the FALLBACK list forever and
never route to the correct rendezvous relays.
- sendPostSync bails before cancel() when a geohash cell has no resolvable
relays, so the composer text + draft are preserved instead of the message
being silently dropped with its draft deleted.
- Teleport detection compares on the common geohash prefix; a cell finer than
the fixed 8-char device fix could never be a startsWith prefix, so the user
was wrongly marked teleported even when physically present.
Performance:
- GeoRelayDirectory.closest precomputes each relay's great-circle distance
once instead of recomputing the trig inside the sort comparator (was
O(n log n) haversine calls over the ~370-relay directory).
- GeohashChatChannel.relays() memoizes the derived set, invalidated by a new
directory version token, instead of re-sorting the whole directory (and
allocating a fresh Set) on every call.
- filterFollowingGeohashChats groups cells by relay into one filter each
(g = [cells]) rather than one REQ per (cell, relay).
Leak/thread-safety:
- FollowingGeohashChatSubAssembler.userJobMap is a ConcurrentHashMap and
endSub now removes the entry (it previously cancelled the jobs but left the
stale entry behind).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
The location-chat nickname was in-memory only (ChannelNewMessageViewModel
.geohashNickname), so it reset to empty on process death. It can't be
recovered from relays either: the anonymous per-cell key publishes no kind-0
profile, and kind-20000 messages are ephemeral, so the nickname (a per-message
`n` tag) has no durable home on the network.
Persist it on-device instead, as a single global handle:
- GeohashChatIdentityState gains nickname()/setNickname(), stored in the
account's encrypted storage next to the per-cell device seed (survives
restarts, switches with the account).
- The chat screen restores it into the composer on room open, and the
nickname dialog writes it back on Save.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
- Center the composer identity avatar vertically instead of bottom-aligning
it (the "post as me" picture was sinking to the bottom of the edit row).
- Halve the gap between the avatar and the text field (drop the avatar's end
padding and nudge the field left into its fixed inset).
- Drop the divider line between the feed and the composer — the other chat
screens don't use one, and the rounded field is separation enough.
- In a location room, suppress the room's own geohash in each message's
footer/time row (every message repeats the room cell, so showing it is
noise). Done via a new LocalChatSuppressGeohash composition-local, factored
into both the footer-has-metadata gate and the footer render; any other
geohash still shows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
The dark-theme primary is a pale pastel, so the follow shield read washed out
versus the original dark #7F2EFF. Use the accent's deep tone (its light-theme
primary) in both themes — carried by inversePrimary in the dark scheme — so the
shield stays a dark, saturated accent with a white figure, matching the
original badge's depth while still following the selected accent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Tying the badge to the tonal-button palette (secondaryContainer) made it read
too dark against profile photos. Match the original badge look instead — a
vivid shield with a white figure — recoloured to the accent: shield = primary,
figure = onPrimary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Adds a compact caption beneath the anonymous composer avatar so the nickname
control is discoverable without knowing the long-press gesture: it shows the
chosen nickname, or a primary-tinted "+ name" prompt when blank. Tapping it
opens the same nickname editor as long-pressing the avatar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
Audit of the rich-text convergence found a behavior regression: the Android
RichTextSegmentRenderer.Payment ignored canPreview and always rendered the
interactive MayBeInvoicePreview / MayBeWithdrawal UI. The original
RenderWordWithoutPreview switchboard deliberately showed plain text for
invoice and withdraw segments when previews are suppressed ("Don't offer to
pay invoices" / "Don't offer to withdraw"). Branch on canPreview for those two;
Cashu and Clink stay unconditional (they decode locally, network only on tap),
matching the original.
Verified: :amethyst play debug compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Replaces the always-on nickname field + "post as me" chip in the location-chat
composer with a single leading identity avatar, next to the text field:
- Shows the account's profile picture when posting as yourself, or an
incognito avatar (person-off) when posting under the anonymous per-cell key.
- Tap toggles anonymous ↔ me (routing through the existing "post as your real
account?" warning when switching to your real identity).
- Long-press opens a small dialog to set/edit the per-cell nickname, so the
nickname field no longer has to sit in the chat all the time.
The read-only "✈ Teleported" indicator moves to its own line above the
composer, shown only when the app has determined you're not in the cell.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
The pre-push spotless gate already skips itself (warn + exit 0) when
`./gradlew spotlessApply` fails for infra reasons, so a restricted sandbox
can't strand a push over formatting. But its detection regex only covered
dependency-resolution/network errors, not the case where the Gradle wrapper
can't download the pinned distribution itself — e.g. an egress-proxy 40x on
`gradle-<ver>-bin.zip`. That surfaced as a hard BLOCK on an XML-only change
even though CI's spotlessCheck would pass.
Add the distribution-bootstrap markers (the `gradle-<ver>-bin.zip` URL, the
Java `Server returned HTTP response code` download exception, and "could not
install gradle") to the infra-skip regex. These strings only appear when
gradlew failed before running any task, so they can't mask a real
formatting/compile failure (verified: a spotlessKotlinCheck violation still
blocks).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157AWJNEzybTuegstbWiQ6B
concord_invite_failed_incompatible is shown for the Unreadable status,
which fires both for a genuinely newer bundle format AND for the far more
common case where the fragment's unlock token does not decrypt the
published kind-33301 bundle (a stale link, or a coordinate re-posted under
a new token). The old copy asserted "created with a newer version of the
app", which is usually wrong, and suggested "try again after updating"
even though this state is non-retryable.
Reword to lead with the likely cause (outdated/replaced link), keep the
newer-version case as a secondary possibility, and point the user at
getting a fresh invite link.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0157AWJNEzybTuegstbWiQ6B
Per review, the follow badge now uses the same two colours as the tonal
buttons (Show more, profile actions): the shield is secondaryContainer and the
inner figure is onSecondaryContainer. The vector is a following(shield, figure)
builder cached per colour at the call site, so the badge follows the accent and
stays visually consistent with the tonal buttons in both themes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Addresses several UX papercuts in the location-chat screens:
- Teleport is no longer a manual composer toggle (which was confusing and
gameable). The app now determines it: when the device's location is known
it compares the current cell to the channel cell (objective); otherwise it
falls back to how the user arrived (map picker = teleported, near-me /
manual = not). The composer shows a read-only "✈ Teleported" indicator only
when you're not in the cell. Reading the permission state never prompts.
- The chat top bar now leads with the city name instead of the raw geohash
code (which users don't care about); the "#geohash · N relays" line is
secondary and tappable, opening a dialog that lists exactly which relays
the cell broadcasts to.
- The home "live near you" bubble resolves the cell to a city name too,
falling back to the #geohash while the reverse-geocode is in flight.
- Own ("purple") messages now show my name/nickname via a new
LocalChatShowSelfAuthorName composition-local — because a geohash chat can
post under two identities (anonymous per-cell key or the real account), the
bubble should say which one each message went out under. Off everywhere
else, so normal chats keep hiding the redundant self name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
Two more on-device fixes:
- Container roles were a faint 0.12/0.16 tint over the surface, so filled
shapes like the Settings icon boxes nearly vanished (accent icon on almost no
background) and tonal buttons looked washed. Retune the container tones to a
moderately saturated fill (mirroring Material's baseline containers, recoloured
to the accent) with near-white content in dark mode, so the settings icon
boxes and profile-header tonal buttons read as white-on-accent again.
- The Following badge keeps its original deep purple (#7F2EFF) instead of
following the accent — it reads as a brand identity mark, and the accent-tinted
version looked flat. Reverted to the fixed two-tone vector.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Three fixes from on-device review:
- Secondary/tertiary CONTAINER roles now derive from the accent (primary)
instead of the purple theme's teal secondary. FilledTonalButton (profile
follow/edit/message, "Show more"/"Show anyway", tonal chips) was neutral
before and had turned teal-on-teal; it now reads as an accent tonal button.
The solid secondary/tertiary roles stay teal (unchanged, as before).
- onPrimary is white again. Deriving it by max contrast made it black on the
light accents used in dark mode, flipping FAB glyphs from white to black;
white reads better on the accent.
- The Following badge is two-tone again: the shield follows the accent while
the inner figure stays white, instead of a flat single-colour shield. The
vector is now a following(accent) builder cached per accent at the call site.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Amethyst's ClickableUrl now delegates plain http(s) links to the shared
commons ClickableUrl (one LocalUriHandler-based implementation for both front
ends); only the Android-specific blossom: case (opening a Blossom media URI via
an Intent) stays local. The public signature is unchanged, so all 12 call sites
are untouched.
Verified: :amethyst play debug compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Brings geohash location channels to full parity with Public Chats (NIP-28),
Relay Groups (NIP-29) and Concord: a top-level list screen reached from the
drawer's "Feeds" section (and pinnable as a bottom-bar tab), rather than the
joined-cells list living inside the "New location channel" builder.
- Route.GeohashChats (plural) + AppNavigation wiring
- GeohashChatsScreen: DisappearingScaffold + AppBottomBar + FAB, a gradient
hero banner, rich per-cell cards (gradient location pin, "#cell", precision
level + resolved city, overflow menu with Open/Leave), and an inviting
empty state
- GEOHASH_CHATS catalog entry now resolves to the list screen; the "+" FAB
opens the builder (near me / manual / teleport)
- GEOHASH_CHATS added to the drawer's Feeds section alongside the other
chat-group types
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
Desktop's ClickableLink and Amethyst's ClickableUrl were near-identical: the only
real differences are mouse-first styling (Desktop underlines + shows a hand
cursor) and the open mechanism. But LocalUriHandler.openUri opens the browser on
Android AND Desktop (and the mail client for mailto:), so the "open a link"
logic never needed to be platform-specific.
Add ClickableUrl/ClickableEmail to commons/ui/components on LocalUriHandler, with
an `underline` flag so each front end keeps its exact look. Desktop's rich-text
renderer now reuses them (underline = true) for url/email/link-preview/withdraw
and its bespoke ClickableLink is deleted. Amethyst keeps its own blossom-intent-
aware ClickableUrl (blossom never applies to plain links, so this path is
equivalent); Phone stays platform-specific (Android dials, Desktop has no dialer).
Verified: :commons JVM and :desktopApp compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Closes the last rich-text fidelity residual. url/email/phone were rendered
generically by the shared core via RichTextInteractions callbacks, losing each
front end's per-type styling and open behavior. They now go through the
RichTextSegmentRenderer strategy:
- Add Url(url, displayText)/Email(address)/Phone(number) to the contract (with
plain-text defaults).
- Core routes LinkSegment(no-preview)/SchemelessUrl -> Url, Email -> Email,
Phone -> Phone; drop the in-core ClickableSpan.
- Amethyst renders them with ClickableUrl/ClickableEmail/ClickablePhone (blossom
intent + dial preserved); Desktop with ClickableLink / underlined mailto / plain
phone text.
- RichTextInteractions now carries only onClickHashtag (the one segment the core
draws itself, with shared icons); the onOpen* callbacks are gone.
Verified: :commons JVM, :desktopApp, :amethyst play debug compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Adds a "Location Channels" category to the bottom-bar settings picker so a
user can pin their joined geohash cells as avatars alongside PublicChat,
RelayGroup and Concord groups. Each pinned cell renders a location-pin
robohash keyed by the geohash, labelled "#<cell>", opening the location
chat when tapped.
- BottomBarEntry.Geohash sealed variant (+ stable key)
- NavBarItem.GEOHASH_CHATS catalog entry in the chats category
- rememberGeohashEntryDisplay resolves the anonymous avatar/route (no
metadata REQ — the cell is anonymous by design)
- picker/settings and live-bar group branches route geohash through the
shared GroupEntryDisplay path
- no feed preloader (the cell's message subscription opens from the chat
screen itself)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
The NIP-05 identifier colour and its verified badge were a fixed purple, and
the image hash-verification "passed" seal was a fixed purple drawable — none
followed the selected accent. Redefine the nip05 token to the theme primary
(covering the identifier text, badge and the other nip05 call sites) and tint
the nip_05 and original drawables with the accent at render.
Also switch the hash-verification "failed" icon from a raw Color.Red to
colorScheme.error, matching the themed error red used everywhere else. Removes
the now-unused Nip05EmailColor light/dark constants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Replaces the two forked cached parsers (amethyst CachedRichTextParser on
android.util.LruCache + desktop DesktopCachedRichTextParser on ConcurrentLruCache
with a naive isMarkdown) with one shared object in commons/jvmAndroid/richtext,
built on quartz ConcurrentLruCache and keeping amethyst's CommonMark-aware
computeIsMarkdown and content-addressed key (content+tags+callbackUri+authorPubKey).
- Add ConcurrentLruCache.trimToSize(maxItems) (+ tests) for the onTrimMemory path.
- Repoint all amethyst callers (incl. the markdown unit test) and both desktop
callers; delete both forks.
Verified: :commons JVM, :desktopApp, :amethyst play debug + unit tests compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
The previous change made every commons action icon follow the accent, but the
liked heart (red) and reposted check (green) are semantic status colours, not
brand purple. Restore their original baked colours and Color.Unspecified
rendering. Only the Following badge — which was brand purple — keeps following
the accent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Implements the cross-platform RichTextSegmentRenderer contract on Desktop
(mouse-first) and converges Desktop onto the shared commons RichTextViewer:
- DesktopRichTextSegmentRenderer draws each divergent segment with the existing
Desktop leaves (AsyncImage media + onImageClick, RenderInvoiceCard/RenderCashuCard,
QuotedNoteEmbed, RenderBechSegment, RenderPdfCard/RenderNowhereLinkCard,
RenderSecretEmoji, relay copy).
- DesktopRichText replaces the hand-rolled DesktopRichTextViewer switchboard: it
parses with DesktopCachedRichTextParser, keeps markdown on RenderMarkdown, and
drives the shared core via the two CompositionLocals. NoteCard repointed.
- Delete the old DesktopRichTextViewer + RenderSegment + the duplicate
RenderCustomEmojiSegment (the core now renders emoji); rename the file.
- Add a NowhereLink method to the contract so both platforms keep their
nowhere.ink card (the core no longer flattens it to a plain link); Android
adapter implements it via NowhereLinkCard.
Verified: :commons JVM, :desktopApp, and :amethyst play debug all compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Give the location-chat entry points a modern, consistent look instead of plain
Material rows:
- New-channel builder: card-based sections with tinted location-pin chips,
icon'd section headers (Your channels / Near me / Enter a geohash), tappable
elevated cards with city + geohash + chevron, and a prominent "Teleport" hero
card. Near-me levels (region → building) render as location cards.
- Teleport screen: the bottom controls become a rounded, elevated sheet floating
over the map, with a location-pin chip, city + #geohash, and a bold
"✈ Teleport here" CTA.
Only existing subset-font glyphs are used (LocationOn, Explore, TravelExplore,
Public, Groups, ChevronRight), so no font regeneration is needed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
The Following, Liked and Reposted vectors baked their own colours (a purple
shield, a red heart, a green check) and were rendered with Color.Unspecified,
so they ignored the theme and stayed those fixed hues under every accent.
Strip the baked colours to a neutral tintable black and tint them at the icon
wrappers with the theme primary, so the follow badge, liked heart and reposted
check now follow the user's selected accent. Call sites that relied on the old
baked colour via Color.Unspecified are pointed at the accent (or keep their
explicit tint, e.g. white-on-coloured-background). The already-monochrome
vectors (Bookmark, Like, Reply, Repost, Search, Share, Zap, ZapSplit) were
already tinted by their callers and are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Closes the fidelity gap and flips Amethyst's rich text onto the shared core:
- Move the hashtag-icon table (HashtagIcon + checkForHashtagWithIcon) into
commons/ui/richtext (the icons were already in commons); amethyst re-exports
it for existing call sites.
- Add a commons custom-emoji renderer (RenderCustomEmoji + InLineIconRenderer)
built on quartz CustomEmoji.assembleAnnotatedList, mirroring CreateTextWithEmoji.
- Wire both into the commons RichTextViewer core (hashtags now show shared inline
icons; emoji render inline).
- Point amethyst's RichTextViewer at CommonsBackedRichTextViewer (the shared core
is now the production plain-text path; ~55 call sites unchanged) and delete the
now-dead private RenderRegular switchboard. Markdown stays native.
Verified: :commons JVM and :amethyst play debug compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Swap the plain geohash composer for the real one — EditFieldRow on a
geohash-aware ChannelNewMessageViewModel. Geohash messages now get the full
compose experience like every other chat: @-mention autocomplete + tagging,
:custom emoji: suggestions and NIP-30 tags, hashtags, quotes, URL references,
image uploads, drafts, and the reply preview — all via the shared parsers
(NewMessageTagger + findHashtags/URLs/NostrUris + findEmojiTags).
ChannelNewMessageViewModel gains a GeohashChatChannel branch in createTemplate
(builds a kind-20000 with the enrichment + g/n/t + reply e-tag) and a geohash
send path that signs with the anonymous per-cell identity (or the account when
posting as self) and mines the fixed 8-bit Bitchat PoW — instead of the account
signer + PUBLIC_CHAT PoW category. Three geohash fields (nickname, teleported,
postAsSelf) drive it; all other channel types are untouched (branch-gated).
GeohashChatViewModel shrinks to the identity/reaction concerns the shared
composer can't express: myPubKeys (acting identity) and the anonymous react
toggle. Teleport / post-as-self / nickname / reply now live on the composer VM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV
Replace the plain text-dropdown accent picker with a row of tappable colour
swatches, each filled with the accent's real primary for the current light/dark
mode so the choice previews the actual result. The selected swatch is marked
with a ring and a check. Adds two small public helpers in the theme
(previewColor, contentColorOnAccent) that reuse the existing accent resolution.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
Implements the commons RichTextSegmentRenderer with Amethyst's existing
touch-first leaves (ZoomableContentView, ImageGallery, LatexEquation,
BechLink/TagLink, MayBe{Invoice,Withdrawal}/CashuPreview/ClinkOffer,
LoadUrlPreview, relay/concord chips, DisplaySecretEmoji), closing over
AccountViewModel/INav/backgroundColor/callbackUri/canPreview.
Adds CommonsBackedRichTextViewer, a parallel entry that parses with the
existing CachedRichTextParser, keeps markdown native, and drives the
shared commons RichTextViewer core via the two CompositionLocals. Left
parallel to the existing RichTextViewer so the core is exercised against
real leaves without touching the ~55 call sites; flipping the entry over
(and deleting the duplicated switchboard) is the reviewed follow-up.
Verified: :commons JVM target and :amethyst play debug both compile.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUiGxXMbjVmgspa1X15o1V
Two cleanup passes on the mobile theme:
- Remove definition-only colour constants from Color.kt that had no references
anywhere (the Primary50..80 purple ramp, DEFAULT_PRIMARY, LIGHT_PURPLE,
Purple700, FollowsFollow, NIP05Verified, the base Nip05EmailColor, DarkerGreen
and LighterRedColor).
- Point the "new item" FloatingActionButton glyphs at onPrimary instead of a
hardcoded Color.White. With a light accent in the dark theme the container is
a pale pastel, so a white glyph was low-contrast; onPrimary resolves to the
correct high-contrast on-colour in both themes. Only FAB glyphs on a primary
container were changed; white icons on dark overlays were left as-is.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1F9jXNwRGTP8qmsV3Wd69
- The author line shows a ✈ marker for teleported senders (not physically in
the cell), folded into the shared display-name resolver so it needs no extra
renderer seam.
- Anonymous reactions now toggle instead of being add-only: if our per-cell key
already reacted with a given content, tapping again retracts it via a NIP-09
deletion signed by the same per-cell key; otherwise it adds the reaction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172JoMccseEKenyWan6txWV