Commit Graph
15448 Commits
Author SHA1 Message Date
ClaudeandVitor Pamplona 43565beb2b style: spotless formatting for CardFeedContentState 2026-06-22 09:38:31 -04:00
ClaudeandVitor Pamplona 2918dc5497 fix: replace LocalDate.ofInstant (API 34) with Instant.atZone chain (API 26+) 2026-06-22 09:38:31 -04:00
Vitor PamplonaandClaude Opus 4.8 a971f4389e perf(notifications): cut per-card composition cost on the notifications feed
The notifications feed is dominated by MultiSetCards, each rendering a gallery
of up to 30 author avatars. Profiling a loaded account showed the per-author
work on the main thread, not the GPU, as the scroll-jitter driver.

Three feature-preserving cuts:

- Hoist account-global reads out of the per-author avatar. The auto-play-gif
  setting and the logged-in follow set were collected once *per author* (~60
  redundant Flow collectors / coroutine launches per card). They are now
  collected once per gallery and passed down via LocalAuthorGalleryRenderContext.

- Dedupe the per-author metadata subscription. Each avatar fired
  UserFinderFilterAssemblerSubscription twice (via observeUserPicture +
  observeUserContactCardsScore); both observers gained a `subscribe` flag so the
  gallery subscribes once per author.

- Replace the per-event DateTimeFormatter day-bucketing in convertToCard with a
  LocalDate.toEpochDay() Long key (identical grouping, no Instant/ZonedDateTime/
  String allocation per reaction/zap/repost), and hoist the ZoneId lookup.

Measured before/after on a Samsung SM-T220 (loaded account, identical scripted
scroll, dumpsys gfxinfo): Slow-UI-thread events ~13 -> ~5 (~60% fewer),
95th-pct frame ~52ms -> ~38ms, janky frames ~13% -> ~10%. GPU timings unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:36:59 -04:00
Vitor PamplonaandGitHub 2187158c53 Merge pull request #3327 from vitorpamplona/claude/disappearing-scaffold-animation-zk9lql
Fix disappearing bar settle logic to prevent blank bands on partial reveals
2026-06-21 16:48:28 -04:00
Claude d96d0220fb feat: gap-safe settle, snappier spring, and jitter dead-zone for disappearing bars
Three refinements to the disappearing top/bottom bar animations:

- Gap-safe settle: settleToNearestEdge could snap a partially-collapsed bar to
  fully hidden whenever it was past the halfway point, even when the content had
  only scrolled part of a bar height (e.g. a gentle flick from the top). Because
  the content padding is fixed and the bar is translated, hiding it further than
  the content scrolled reopens the same blank band the reveal-damping fix removed.
  Each bar now latches whether it has actually reached its hidden edge through
  scrolling; the settle only commits to fully hidden when that latch is set,
  otherwise it settles back into view. This keeps the deliberate-reveal behavior
  (a sub-halfway reveal after fully hiding still snaps back hidden) without the gap.

- Snappier settle spring: StiffnessMediumLow -> StiffnessMedium so the bars
  resolve to their edge with a quick native snap instead of a slow float. Still
  DampingRatioNoBouncy, and overshoot stays clamped by animateOne's bounds.

- Micro-scroll dead-zone: ignore sub-pixel scroll attempts so jitter doesn't
  nudge the bars or flip the binary status-bar toggle. Kept tiny and symmetric so
  the reveal never lags the content enough to open a gap.

Proportional top/bottom collapse was intentionally left out: both bars already
move at the same pixel rate (visual lock-step for their shared travel), and
forcing the shorter bar to finish at the same time as the taller one would push
it off the 1:1 content track and reintroduce a gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jbLnoWks19ottXNrMZ6DH
2026-06-21 20:32:03 +00:00
Claude 73d52401cd fix: remove reveal damping so the top bar stays glued to content
The 0.5 reveal-sensitivity damping in DisappearingBarNestedScroll made the
bars reveal at half the rate they hide. Because the content scrolls 1:1, the
bar offset would lag behind the content scroll: after hiding the chrome and
scrolling back up to the top, the list reaches its top while the bar is still
only half revealed, leaving a blank band between the bar and the first item.

The bar's translationY must mirror the content scroll offset exactly so its
bottom edge stays glued to the first item's top edge. Any persistent reveal
damping breaks that invariant and opens the gap, so revealing now tracks the
finger 1:1 like hiding does.

The original goal of the damping — keeping the chrome from popping back on the
tiny reverse drag a finger makes when it catches a fast scroll — is still met
without breaking the 1:1 invariant: a partial reveal that doesn't cross the
halfway point is snapped back to the hidden edge by settleToNearestEdge on
fling/lift, and the binary OS status bar is already debounced by the
show/hide hysteresis in the scaffold.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011jbLnoWks19ottXNrMZ6DH
2026-06-21 19:35:30 +00:00
Vitor PamplonaandGitHub abd540e3c2 Merge pull request #3326 from vitorpamplona/claude/relaxed-meitner-3yr85y
Refactor command dispatch: extract Router helper, remove Commands.kt
2026-06-21 14:39:03 -04:00
Vitor PamplonaandGitHub 1909f402ab Merge pull request #3325 from vitorpamplona/claude/feed-media-prefetch
feat(feeds): prefetch media + pre-parse text ahead of the viewport
2026-06-21 14:36:50 -04:00
Vitor PamplonaandClaude Opus 4.8 df1982a7f6 feat(feeds): prefetch media + pre-parse text ahead of the viewport
Warms the next/previous few feed notes off the main thread so media and
link previews are ready before the user scrolls to them, and pre-parses
rich-text bodies into the shared cache so scroll-time composition is a
cache hit instead of a UI-thread parse.

Per upcoming note (off Dispatchers.Default, deduped via a per-feed set,
cancelled on a new visible range):
- Pre-parses TextNote/Comment bodies through CachedRichTextParser using the
  renderer's exact key, so the composition reads a cached parse. Other kinds
  still get their media/links discovered, just without the render-cache warm.
- Prefetches images into Coil — inline content images, NIP-92 imeta blobs,
  NIP-94 file-header url tags, and video poster frames — and records each
  decoded aspect ratio in MediaAspectRatioCache so the box is reserved on
  first layout (no jump). Video poster ratios seed the video URL's box too.
- Warms OpenGraph/link previews via UrlCachedPreviewer.
Gated on showImages()/showUrlPreview() so it honors data-saver/Wi-Fi-only.
Video bytes are deliberately not prefetched (large, HLS, player pool already
starts fast).

Wired centrally at the two feed dispatchers (RenderFeedContentState,
RenderFeedState) so every list feed routed through them is covered without
per-screen wiring, plus direct hooks for the custom-render feeds (hashtag,
profile notes) and a LazyGridState variant for the grid feeds (gallery,
products, discover).

CachedRichTextParser is now content-addressed (memoized contentHash on
ImmutableListOfLists) so an off-thread pre-parse maps to the same entry the
renderer looks up, and its cache grows 50 -> 500 to hold the prefetch +
multi-feed working set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 13:44:39 -04:00
Claude 48b988d2f7 refactor(cli): simplify amy dispatch, Context lifecycle, and store handling
Four mechanical simplifications to amy with no change to the public
CLI/JSON contract:

1. Drop the Commands.kt pass-through layer. Main.kt now calls each
   command object directly; the file is repurposed into Router.kt,
   holding a single shared `route(name, tail, usage, routes)` helper.

2. Replace the `Context.open(dataDir)` + `try { } finally { ctx.close() }`
   boilerplate (~46 sites) with `Context.open(dataDir).use { ctx -> }`
   now that Context is AutoCloseable.

3. Remove the reflection-based `storeIsInitialized()` in Context; track
   the lazy event store via `Lazy.isInitialized()` instead.

4. Route every `*Commands.dispatch` through the `route` helper, dropping
   the repeated empty-check + unknown-verb `when` boilerplate.

Net -282 lines. Docs (cli/DEVELOPMENT.md, amy-expert skill + template)
updated to the new wiring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjEvS812aPLZ6nM2XLobzF
2026-06-21 17:24:00 +00:00
Vitor PamplonaandGitHub fa11a94613 Merge pull request #3323 from vitorpamplona/claude/powr-workout-interop-r1efh0
Add POWR / NIP-101e strength workout interop (read-only)
2026-06-21 11:52:55 -04:00
Vitor PamplonaandGitHub c503d2ee56 Merge pull request #3324 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-21 11:40:30 -04:00
Claude 35af6ca580 fix(fitness): store kind-33401 exercise templates in LocalCache
justConsumeInnerInner's when(event) has no generic addressable fallback —
its else branch logs "Event Not Supported" and drops the event. A kind-33401
ExerciseTemplateEvent arriving from a relay was therefore never stored as an
AddressableNote, so the fetched template never attached to its placeholder
note and the workout card's exercise title never resolved. Dispatch it to
consumeBaseReplaceable like the other addressable definitions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJwXz6CWez8r7trgHXT545
2026-06-21 15:39:55 +00:00
vitorpamplonaandgithub-actions[bot] 4aa713e2db chore: sync Crowdin translations and seed translator npub placeholders 2026-06-21 15:36:45 +00:00
Vitor PamplonaandGitHub d25f80bcb3 Merge pull request #3321 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-21 11:35:14 -04:00
vitorpamplonaandgithub-actions[bot] eb04e09d26 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-21 14:32:14 +00:00
Vitor PamplonaandGitHub 7b93bfb5b9 Merge pull request #3313 from vitorpamplona/claude/recommended-apps-search-3628xk
Add search and follow-list filtering to app recommendations
2026-06-21 10:30:39 -04:00
Vitor PamplonaandGitHub 5f2e879d31 Merge pull request #3314 from vitorpamplona/claude/public-chat-notifications-qmn4gh
Notify on public chat replies without p-tags
2026-06-21 10:29:54 -04:00
Vitor PamplonaandGitHub d34453ec17 Merge pull request #3312 from vitorpamplona/claude/share-options-drawer-70nrq4
Extract share options to reusable bottom sheet
2026-06-21 10:27:36 -04:00
Claude 278dac7fea Merge remote-tracking branch 'origin/main' into claude/recommended-apps-search-3628xk
# Conflicts:
#	amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/apps/recommendations/ProfileAppRecommendationsScreen.kt
#	amethyst/src/main/res/values/strings.xml
2026-06-21 14:11:37 +00:00
Vitor PamplonaandGitHub 43f8919ce3 Merge pull request #3322 from vitorpamplona/claude/app-recommendations-search-h8bklq
feat: add a search box to the App Recommendations screen
2026-06-21 09:58:32 -04:00
David KasparandGitHub 4a577485cf Merge pull request #3319 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-21 11:56:34 +01:00
Claude eaa6c147e4 feat: add a search box to the App Recommendations screen
The Recommended Apps screen listed every discovered app definition in a
single sorted LazyColumn with no way to filter, so finding a specific app
to recommend meant scrolling the whole list. Users couldn't find a search
because there wasn't one.

Add an always-visible outlined search field below the description that
filters the list by app name (case-insensitive). Rows whose kind 31990
definition hasn't arrived yet have no name to match, so they only appear
when the box is empty. A dedicated empty state shows when a query matches
nothing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EjuK6g3KNkyEEnti6JtDEi
2026-06-21 02:40:00 +00:00
vitorpamplonaandgithub-actions[bot] 3dcc46de2b chore: sync Crowdin translations and seed translator npub placeholders 2026-06-21 02:25:11 +00:00
Vitor PamplonaandGitHub 6227cfd220 Merge pull request #3320 from vitorpamplona/claude/modest-mayer-8mo1zd
feat: add Notification setting to show or hide Messages on the Notification tab
2026-06-20 22:22:58 -04:00
Vitor PamplonaandGitHub da5525c15d Merge pull request #3318 from vitorpamplona/claude/reply-reaction-android-tray-9bkgcx
fix: surface reply, not root, for multi-e-tag reactions in Android tray
2026-06-20 22:21:09 -04:00
Claude 966ba17455 fix: surface reply, not root, for multi-e-tag reactions in Android tray
A kind:7 reaction can carry several `e` tags (e.g. the thread root plus
the actually-reacted-to reply). Per NIP-25 the last `e` tag is the event
being reacted to, but the notification consumer resolved the reacted note
via `originalPost().firstOrNull()`, which picked the root. The tray then
showed the like as if it targeted the root note instead of the reply.

Switch to `lastOrNull()` to match NIP-25 and the in-app reaction card,
which already resolves the target with `note.replyTo?.lastOrNull()`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EEQN8Sy7mQ81BEC3rUu5o
2026-06-21 00:17:46 +00:00
Claude bd8beb43e1 feat: add Notification setting to show or hide Messages on the Notification tab
Adds a "Show Messages" toggle to the Display section of Notification Settings.
When disabled, direct/group messages (NIP-17 chats, NIP-04 DMs, and Marmot
group messages) are filtered out of the Notification tab, keeping them only in
the Messages tab. Defaults to on, preserving existing behavior.

The setting is persisted per-account in AccountSettings/LocalPreferences and is
included in the notification feed key so the feed refreshes immediately when
toggled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018FwwcaBRyvnJFWaDLoQ16d
2026-06-20 20:27:32 +00:00
Claude e3d3cebdd4 refactor(notifications): tighten public-chat reply walk and its docs
Audit follow-ups, no behaviour change:

- isNotifiablePublicChatReply bails before allocating the HashSet/ArrayDeque
  scratch when the channel message has no parents (top-level posts — the
  common case), and builds the deque straight from the parent list.
- Correct the docstring: a kind-42 replyTo holds only the immediate parent
  (the channel root is filtered out), so the chain is walked hop-by-hop
  through each cached ancestor — the previous wording implied replyTo already
  held the ancestors.
- Trim the over-long inline comment on the acceptableEvent gate.
- Make the multi-hop test prove what it claims: assert the immediate parent
  is not me, so the walk only passes by climbing to the grandparent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PxDVCSWe1RwZ51vABBwqbG
2026-06-20 20:08:08 +00:00
Claude 45656e5476 docs: clarify observeNotes replacement caveat; hoist initial-value scan
Audit follow-up. The previous comment claimed an in-place addressable
replacement "not re-emitting here is fine" because AppRow watches each note;
that conflated per-row content (which does stay live) with list membership and
sort order (which do not re-evaluate on a kind-31990 replacement). Documents
the actual behavior and why it is acceptable. Also hoists the nested
remember{} initial-value cache scan out of the collectAsStateWithLifecycle
argument for readability; no behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8oLe5cMzSY8AzTiULJZiW
2026-06-20 20:00:33 +00:00
Claude 8c56bc64c8 feat(fitness): render kind-33401 exercise templates as a card
A standalone POWR exercise template (kind 33401) opened via naddr, a quote,
or in a thread previously fell through to RenderTextEvent, showing only its
bare instruction text with no title. Add ExerciseTemplateDisplay (equipment
icon, title, equipment/difficulty subtitle, instructions via the rich-text
pipeline) and dispatch to it from NoteCompose.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJwXz6CWez8r7trgHXT545
2026-06-20 19:56:49 +00:00
Claude b835819f97 refactor: de-duplicate shareId and correct ShareActionRows docs
Hoist the AddressableNote-vs-id shareId computation out of the two image
share rows, and fix the KDoc rationale: "Share as Image" shares a local PNG
(no relay write); the rows are hidden on private rumors because they expose
the note's content publicly, not because they publish an e-tag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc7PP3PLwT4spvjB2c72pk
2026-06-20 19:37:00 +00:00
Claude a8feb88c2e refactor: derive app list from observeNotes, drop tick + rescan
The screen previously used observeNewEvents purely as an invalidation tick
to re-scan LocalCache.addressables on every app-definition insertion. Since
observeNotes already seeds with the cached kind-31990 notes and re-emits as
new ones arrive, the candidate list now derives straight from those notes:
the appDefinitionsTick counter and the repeated full-cache rescan are gone.
Initial value is seeded from the current cache snapshot to preserve the
first-frame content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8oLe5cMzSY8AzTiULJZiW
2026-06-20 19:30:33 +00:00
Vitor PamplonaandGitHub cdfe007fb4 Merge pull request #3309 from vitorpamplona/claude/remove-alt-tags-events-786j5p
Remove unused NIP-31 alt tag surrogate code and clean up imports
2026-06-20 15:26:00 -04:00
Claude e2f2c4654e feat(fitness): resolve POWR exercise templates (kind 33401)
Render real exercise names in POWR workout cards instead of slug-derived
labels by fetching the referenced kind-33401 exercise templates.

- ExerciseTemplateEvent (kind 33401, addressable) with title/format/
  format_units/equipment/difficulty accessors; registered in EventFactory.
- WorkoutRecordEvent now implements AddressHintProvider: linkedAddressIds
  (deduped 33401 + 33402 coordinates) seed the gatherer so the card
  re-renders when a template arrives, and addressHints feed the relay-hint
  index with the relay.powr.build hints so the fetch reaches where POWR
  published the templates.
- WorkoutDisplay resolves each exercise via LoadAddressableNote +
  observeNoteEvent<ExerciseTemplateEvent>, showing the template title once
  fetched and the slug until then.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJwXz6CWez8r7trgHXT545
2026-06-20 19:24:25 +00:00
Claude 2ec5c81fd3 perf(notifications): keep the public-chat reply gate inline so it short-circuits
Hoisting the p-tag/public-chat-reply check into a pre-computed val made it
eager: the tag scan (and, for channel messages, the reply-chain walk) ran on
every Note in the cache, even the overwhelming majority rejected by the cheap
`kind in NOTIFICATION_KINDS` check. Inline it back into the && chain in its
original 4th position so that kind check short-circuits ahead of it, and order
the OR so the cheaper tag scan runs before the reply-chain walk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PxDVCSWe1RwZ51vABBwqbG
2026-06-20 19:22:23 +00:00
Claude 445a20b2f3 fix: preserve user-provided media descriptions for accessibility
The blanket NIP-31 alt removal also dropped genuine accessibility
descriptions (image descriptions for the blind) on a few media paths where
the user's caption was only stored in the event-level alt tag. Restore them
via the proper, non-deprecated fields:

- NIP-94 FileHeaderEvent (kind 1063) and NIP-17 encrypted file headers
  (kind 15): write the `alt` tag only when the user actually provided a
  caption (the NIP-94 accessibility description), never the old boilerplate
  fallback.
- MIP-04 encrypted group media (kind 9): route the caption into the imeta
  `alt` field via buildMip04IMetaTag instead of an event-level alt tag.

Re-adds the narrowly-scoped TagArrayBuilder.alt() / AltTag.assemble() write
helpers (documented as accessibility-only, not for deprecated NIP-31
boilerplate). Boilerplate alt tags on all other event kinds remain removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xAESAz1H1VNjmQpMVqBXj
2026-06-20 19:19:16 +00:00
Vitor PamplonaandGitHub 6235c407c3 Merge pull request #3311 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-20 15:16:31 -04:00
Claude 21805f6857 refactor: use indexed observeNewEvents for app definition ticks
Replaces the global newEventBundles firehose (woke on every new event of
every kind, then filtered for AppDefinitionEvent client-side) with the
indexed LocalCache.observeNewEvents for kind 31990, so the cache delivers
only app-definition insertions to the recompute tick.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8oLe5cMzSY8AzTiULJZiW
2026-06-20 19:13:30 +00:00
vitorpamplonaandgithub-actions[bot] 64eeeb84bc chore: sync Crowdin translations and seed translator npub placeholders 2026-06-20 19:11:55 +00:00
Vitor PamplonaandGitHub 1530cae4eb Merge pull request #3308 from vitorpamplona/claude/localization-unused-strings-3xsyhu
i18n: localize Marmot group chat strings and remove unused resources
2026-06-20 15:09:57 -04:00
Vitor PamplonaandGitHub e9bc3dcf0f Merge pull request #3310 from vitorpamplona/claude/android-tray-dismiss-vbdhve
Auto-dismiss notifications when events are read in-app
2026-06-20 15:05:09 -04:00
Claude 81a1c3b4c4 feat: auto-dismiss tray notifications when the event is read in-app
Tray notifications were only suppressed for new events while the app was
foregrounded (MainActivity.isResumed); notifications already posted while
backgrounded lingered even after the user opened the app and viewed them.

Per-event tray notifications are keyed by the triggering event's
id.hashCode(), so when a note bearing such an id is marked read in-app
(loadAndMarkAsRead's onIsNew branch), cancel the matching notification.
Childless group summaries are cleaned up so the tray doesn't keep an
empty summary around. Covers replies/mentions (Notification feed via
NoteCompose) and DMs (ChatMessageCompose); grouped reaction/zap cards
have no 1:1 event mapping and are left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XsnDifyUtaWUq4wGu4smZf
2026-06-20 17:51:54 +00:00
Claude c06d8eb7f7 refactor: make the Share drawer share-only; 3-dot menu keeps its copy rows
Per review, the shared element is now just the three true Share options
(browser link, image file, image URL) in ShareActionRows, surfaced by the
ShareOptionsBottomSheet drawer.

- The 3-dot menu keeps its four copy-to-clipboard rows inline, exactly as
  before; its share section is now a single "Share" row that opens the drawer
  instead of doing the share directly.
- NoteDropDownMenu owns the drawer state and renders the sheet in place of the
  menu dialog, so the existing direct callers (MultiSetCompose,
  MessageSetCompose) need no changes.
- The reaction-row Share button opens the same drawer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc7PP3PLwT4spvjB2c72pk
2026-06-20 16:49:52 +00:00
Claude b9ac370f4f feat(fitness): render POWR kind-1301 strength workouts
POWR and RUNSTR both publish kind 1301 but with incompatible tag schemas.
A POWR event previously rendered with the raw "33401:...:back-squat-bb"
coordinate as its activity label, no duration, and none of the set data.

Parse the POWR / NIP-101e dialect in quartz and render it in Amethyst:

- type tag for the activity (strength/circuit/emom/amrap), preferred over
  the RUNSTR exercise verb; coordinate-form exercise tags no longer leak as
  a verb.
- start/end session timestamps -> derived duration; completed flag.
- structured per-set exercise tags (kg weights, reps, rpe, set_type),
  grouped per exercise template with volume/top-weight aggregates.
- WorkoutDisplay now shows Exercises/Sets/Volume stats and a per-exercise
  breakdown (e.g. "Back Squat Bb -> 3 x 8 x 84 kg") in the viewer's unit.

Rendering interop only; Amethyst still publishes the RUNSTR-canonical form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QJwXz6CWez8r7trgHXT545
2026-06-20 16:47:39 +00:00
Claude 9a43e03748 feat: add top-nav feed filter to recommended apps screen
Wires the shared FeedFilterSpinner into the Recommended apps (NIP-89)
screen so it matches the other feed screens. The selection persists per
account via a new defaultAppRecommendationsFollowList setting and resolves
through the existing topNavFilterFlow machinery. App definitions only carry
an author dimension, so the Follows-style filters narrow the list to apps
made by those authors (matchAuthor); hashtag/relay/community variants are
no-ops on apps, as expected. Combines with the local text search and adds a
filter-empty message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8oLe5cMzSY8AzTiULJZiW
2026-06-20 16:47:23 +00:00
Claude 18f736abf7 refactor: stop writing deprecated NIP-31 alt tags on events we create
The NIP-31 event-level "alt" tag is deprecated, so Amethyst no longer
emits it on any event it builds. Removed all `alt(...)` builder calls and
`AltTag.assemble(...)` insertions across every event kind in quartz (and
the few app-side builders), along with the now-unused `ALT`/`ALT_DESCRIPTION`
companion constants and the `TagArrayBuilder.alt()` / `AltTag.assemble()`
write helpers.

Reading alt tags from incoming events is kept (AltTag.parse/match,
TagArray.alt(), Event.alt()) for interop with clients that still send them,
and the imeta media accessibility `alt` field (NIP-92/94) is untouched.

Updated/removed tests that asserted alt-tag presence and refreshed the
deterministic event-id/sig golden masters in UpdateMetadataTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xAESAz1H1VNjmQpMVqBXj
2026-06-20 16:46:25 +00:00
Vitor PamplonaandGitHub e20d7d2f0f Merge pull request #3307 from vitorpamplona/claude/custom-emojis-push-notifications-8n3h87
feat(notifications): render NIP-30 custom emoji reactions as avatar badge
2026-06-20 12:43:27 -04:00
Claude 9cafbf02cc feat(notifications): notify on public chat replies without a p-tag
Public chats (NIP-28, kind 42) routinely reply to a user without adding a
`p` tag, so the existing mention gate (Event.isTaggedUser) silently dropped
them from both the in-app Notifications feed and Android tray push.

Add NotificationFeedFilter.isNotifiablePublicChatReply: a cache-only check
that walks a channel message's reply chain for one of the user's own
messages — covering a direct reply to my message ("the previous message was
mine") and later messages in a thread I'm already part of ("an active
thread"). It is bounded (depth + visited-set) and reads only Note.replyTo,
so the push dispatcher and the feed can both consult it without loading the
account or decrypting anything.

Wire it as an OR alongside the p-tag gate in the three relevance sites that
share the rule — NotificationFeedFilter.acceptableEvent, the
NotificationDispatcher observer predicate, and EventNotificationConsumer —
while keeping tagsAnEventByUser as the scoping AND so unrelated channel
chatter never leaks through, even in Global mode.

Route ChannelMessageEvent to its own tray handler: reply-to-me renders as a
threaded reply (with inline reply action) grouped by channel so a busy room
collapses into one notification; a pure p-tag citation still renders as a
mention. Muting a thread suppresses it via the existing isAcceptable gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PxDVCSWe1RwZ51vABBwqbG
2026-06-20 16:39:54 +00:00
Claude 62ca086302 feat: open a Share options bottom drawer from the reaction-row Share button
Tapping Share in a note's reaction row now opens a bottom drawer with the
same Copy & Share options as the 3-dot menu (Copy Text, Copy Author ID,
Copy Note ID, Copy raw JSON, Share link, Share as Image, Share as Image
URL) instead of jumping straight to the system share sheet.

The seven rows are extracted into a shared ShareCopyActionRows composable so
the 3-dot menu and the new ShareOptionsBottomSheet stay in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jc7PP3PLwT4spvjB2c72pk
2026-06-20 16:28:29 +00:00