Commit Graph
15784 Commits
Author SHA1 Message Date
Claude e70688841c fix: preload favorited nsite/napplet manifests in the browser
Favorites store only the addressable coordinate (kind:pubkey:dtag); the
launch path re-resolves the live event from LocalCache at tap time. When
that event hadn't streamed in yet, tapping a favorited nsite/napplet showed
"isn't loaded yet" and never recovered on its own — the only thing that
pulled the event into the cache was visiting the nsite/napplet feed (it
subscribes by author), which is why opening that feed and coming back made
the favorite suddenly launchable.

Add PreloadFavoriteNostrApps, which subscribes each favorited coordinate to
the shared EventFinder (the same lifecycle-aware loader observeNote uses) so
the manifests fetch via the author's outbox relays as soon as the launcher
opens. Wire it into the Browser tab and the Favorite Apps tab. The loader
drops each coordinate once its event arrives, so this is a one-shot fetch,
not a standing feed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XH7D6xUhHDKUbrYHoBnZyL
2026-06-26 20:53:46 +00:00
Vitor PamplonaandGitHub 52a467db5f Merge pull request #3387 from vitorpamplona/claude/zap-card-bg-color-kz8szr
Fix activity card background propagation to embedded notes
2026-06-26 16:26:02 -04:00
Claude d6fc3232b2 fix: keep zap/reaction card inner content on the card's tint
The embedded note and comment inside the lightning / nutzap / onchain-zap
and reaction activity cards were handed the parent feed / MultiSetCard
background state, so they drew black (the app background) or flashed the
new-note highlight instead of letting the card's orange (or like-tinted)
wash show through.

ActivityCardFrame now exposes a stable transparent background state to its
content for the inner note and comment to draw on; the hand-built onchain
card uses a matching local state. Nothing inside the card's layout changes
background from the feed anymore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GEUsgVr5e31qYeqNSjgHif
2026-06-26 19:37:43 +00:00
Vitor PamplonaandGitHub 19debffe15 Merge pull request #3384 from vitorpamplona/claude/nsite-napplet-icons-okz5y6
Show nSite/nApplet icons from bundled blobs in favorites
2026-06-26 15:16:43 -04:00
Claude c7e14676f2 Merge remote-tracking branch 'origin/main' into claude/nsite-napplet-icons-okz5y6 2026-06-26 19:11:40 +00:00
Claude e8539df81e Merge remote-tracking branch 'origin/main' into claude/nsite-napplet-icons-okz5y6
# Conflicts:
#	amethyst/src/main/res/values-fr-rCA/strings.xml
2026-06-26 19:07:03 +00:00
Vitor PamplonaandGitHub b703330c6a Merge pull request #3386 from vitorpamplona/claude/amethyst-lint-translations-dend0l
Move browser strings to commons for shared localization
2026-06-26 15:06:54 -04:00
Vitor PamplonaandGitHub 7fc5c92760 Merge pull request #3385 from vitorpamplona/fix/account-dir-cleanup-on-delete
fix: delete per-account dir on account removal + sweep orphans at startup
2026-06-26 15:05:42 -04:00
Claude 0092b4f726 perf: single-pass icon-path selection; resolve on event not NoteState
NappletIconPath.choose scanned the whole path list once per conventional name
(O(PRIORITY x N)), allocating a filter list and recomputing each basename up to
20 times per path. A manifest is a whole static site, so N can be large. Collapse
to one O(N) pass with a name->rank map: basename computed once per path, no
intermediate lists, and the loose-raster fallback is skipped entirely once an
exact match is in hand. Behavior is unchanged (the 11 selection tests still pass).

Also key the favorite-icon resolution on the manifest event rather than the whole
NoteState, so paths()/choose() don't re-run on unrelated metadata bumps (a
reaction or zap tracked on the note).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaPdt8EnSrSBuzdudrxzTH
2026-06-26 19:00:53 +00:00
Claude 5a297f17a1 fix: move orphaned browser/napplet translations to :commons
The browser/napplet strings (browser_address_hint, browser_console_title,
browser_console_title_short, browser_console_clear, napplet_untitled) were
moved to :commons, but their per-locale translations were left behind in
amethyst's values-*/strings.xml. With the default keys gone from amethyst,
lint flagged them as ExtraTranslation (80 errors across 16 locales).

Move the translations into commons/src/androidMain/res/values-*/strings.xml
so the default key and its translations live in the same module, preserving
the existing translation work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uza7sGxYPZtY43Ln2yH8FQ
2026-06-26 18:58:08 +00:00
Vitor PamplonaandGitHub 0afc83dcfb Merge pull request #3383 from vitorpamplona/claude/amethyst-draft-signing-bug-9cx0zx
Fix draft deletion by holding strong reference to draft notes
2026-06-26 14:52:24 -04:00
Vitor PamplonaandClaude Opus 4.8 58b551265c fix: delete per-account dir on account removal + sweep orphans at startup
Account deletion cleaned up the saved-accounts list and encrypted prefs but
never removed the on-disk files/accounts/<pubkey>/ directory (the MLS/Marmot
stores created in AccountCacheState.loadAccount). Every deleted or logged-out
account leaked its folder, so the on-disk account count drifted far above the
number of accounts shown in the switcher (16 dirs vs 6 saved on a test device).

- AccountCacheState: add deleteAccountFiles(pubkey) to remove the directory and
  pruneOrphanAccountDirs(keepPubkeys) to clear dirs no longer backed by a saved
  account.
- AccountSessionManager.logOff: delete the files in both delete branches.
- AppModules: one-time startup sweep, keyed by the hex of every saved account,
  to clean up folders leaked before this fix.
- LocalPreferences.savedAccounts(): make the lazy init race-safe with a
  dedicated mutex + double-checked locking. Multiple startup coroutines
  (account load, always-on notification service, the new orphan sweep) call it
  concurrently; the old check-then-act could run the IO read in parallel and
  double-write ALL_ACCOUNT_INFO during the legacy migration.

Verified on-device: 16 -> 6 account dirs after one launch, stable across
restarts, no startup regressions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 14:47:23 -04:00
Claude 119517bd3a fix: only resolve the draft note when there is a version to save
The versions collector resolved draftNote = account.getOrCreateDraftNote(...)
on every emission, including the content-less initial tick (~1s after the
composer opens). That touched the lateinit account before any user input; if
it were ever unset at that moment the throw would kill the collectLatest
coroutine and silently stop all draft saves for that composer.

Move the resolve inside the `if (it > 0)` guard, co-located with the save, so
account is only read once a real edit exists. The open-a-draft-then-send-
without-editing case is still covered by the explicit refresh in load().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 18:41:23 +00:00
Claude bb968f74f2 fix: make nsite/napplet favorite icon reactive to manifest arrival
The first cut resolved the manifest once inside remember(coordinate), so if the
event wasn't in LocalCache at first composition (e.g. a cold start, where it
streams in from relays a moment later) the null result was cached for the life
of the composition and the icon never appeared — a silent fall-back to the glyph.

Observe the addressable note's metadata StateFlow instead, mirroring the webapp
favicon path (which re-resolves on the BrowserIconRegistry key set): when the
manifest lands or updates in LocalCache, the icon resolves and the blob fetch
kicks off. checkGetOrCreateAddressableNote returns null only for a malformed
coordinate, so the early return stays structurally stable per call site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaPdt8EnSrSBuzdudrxzTH
2026-06-26 18:23:52 +00:00
Claude 94b99e0973 refactor: derive the draft note from the versions flow, decouple DraftTagState
DraftTagState goes back to pure tag/version state — it no longer knows about
AddressableNote or needs an account-aware builder. Instead each composer
derives draftNote from the debounced versions collector it already runs:
on each emission it maps the current tag to its live cache note via
account.getOrCreateDraftNote(current). The ViewModel field holds the strong
reference that keeps LocalCache's weak entry alive until a deletion needs it.

load() refreshes draftNote after set(oldTag) because set() doesn't bump
versions, covering the open-a-draft-then-send-without-editing case.

deleteDraftInner takes the nullable note again and still only signs when the
note holds a real, non-deleted DraftWrapEvent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 18:16:42 +00:00
Vitor PamplonaandGitHub 799bbe68ef Merge pull request #3382 from vitorpamplona/claude/amethyst-logo-redesign-nlefdg
Redesign service notification icon with network topology
2026-06-26 14:11:55 -04:00
Claude 5b8a42020c feat: adopt orbit-ring service notification icon; drop preview scaffolding
Make the orbit design (centred gem ringed by three "server" nodes) the
always-on relay-service notification icon by writing it into the existing
amethyst_service drawable, so NotificationRelayService picks it up unchanged.

Remove the icon bake-off scaffolding now that a design is chosen:
- delete the alternative drawables (amethyst_service2..6)
- delete the DEBUG-only ServiceIconPreviewNotifications helper
- drop its trigger and now-unused imports from MainActivity

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 18:04:42 +00:00
Claude 6b6ee26a88 style: maximize centred gem in the service-icon options
Grow the central Amethyst gem in each motif (orbit/sync/hub/waves) to the
largest size that still clears the surrounding elements. For the hub icon the
spokes now start farther from the centre to give the gem room.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 17:57:31 +00:00
Claude 111686884a fix(i18n): drop CDATA from French "Block & Hide" strings
block_hide_user and report_dialog_block_hide_user_btn wrap their value in
<![CDATA[...]]> in the English source only because the text contains a literal
'&' (Block & Hide User). The French translations kept the CDATA wrapper but the
text has an apostrophe (l'utilisateur) instead — and AAPT2 fails to flatten a
CDATA-wrapped apostrophe ("Can not extract resource from ParsedResource" /
"Invalid unicode escape sequence"), breaking mergePlayDebugResources.

Drop the now-pointless CDATA and use a normally-escaped apostrophe (l\'utilisateur)
— identical runtime string, valid under every AAPT2 version, and consistent with
the escaped-apostrophe style the rest of these files already use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaPdt8EnSrSBuzdudrxzTH
2026-06-26 17:45:21 +00:00
Claude eb6dfc7a4a feat: show nsite/napplet bundled icon on favorite tabs
Favorited web apps replace the bottom-nav glyph with the site's favicon, but
nsites (NIP-5A) and napplets (NIP-5D) fell back to the generic grid glyph.

The webapp trick (capturing onReceivedIcon from the WebView) can't work here:
nsites/napplets render inside a cross-origin sandboxed iframe under a trusted
shell, so onReceivedIcon only ever reports the shell's main-frame icon, never
the applet's. Instead, derive the icon from the manifest's own bundled blobs —
content-addressed, sha256-verified, and Tor-routed like the rest of the site.

- quartz: NappletIconPath picks the best conventional icon path (favicon/icon/
  apple-touch-icon, raster over ico/svg, shallower path wins) from a manifest's
  path tags; exposed as iconBlob() on the four nsite/napplet event kinds. Unit
  tested.
- amethyst: rememberNappletIconModel resolves the live manifest from LocalCache,
  prefetches the icon blob into the shared verified cache off the composition
  thread, and returns a file:// model. AppBottomBar and FavoriteAppsScreen feed
  it into FavoriteAppIcon for NostrApp favorites, mirroring the webapp path.

Priority: bundled blob > manifest icon URL tag > type glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaPdt8EnSrSBuzdudrxzTH
2026-06-26 15:55:12 +00:00
Claude 002c8b6d30 style: enlarge service-icon options to fill the icon frame
Scale up the orbit/sync/hub/waves motifs and their centred gems so each
drawing occupies as much of the 512 viewport as possible (rings and nodes
pushed near the edge, larger central gem), keeping a small margin so strokes
don't clip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 15:53:11 +00:00
Claude b5706564d9 refactor: build the draft note from the tag inside DraftTagState
Instead of capturing the AddressableNote returned by each save (and on
load) and re-assigning it, DraftTagState now builds the note from the
current tag via an injected (tag -> AddressableNote) builder and rebuilds
it whenever the tag changes. Because that note is the live cached object
for the address, its event tracks the draft automatically as it is saved
or removed, so:

- the note is never null inside the state (lateinit, wired by start());
- createAndSendDraftIgnoreErrors no longer needs to return the note, and
  load no longer needs to capture it — set(oldTag) rebuilds it;
- the writer's existence check becomes "is there a real, non-deleted
  draft event in the note" (DraftWrapEvent.isDeleted()), which also stops
  a second blank-delete from re-signing an already-emptied draft.

ViewModels just wire draftTag.start(account::getOrCreateDraftNote) in
init() and reference draftTag.note; the per-VM field and held() are gone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 15:38:55 +00:00
Claude e1c152ada8 feat: add four more service-icon options + on-device preview harness
Add four additional always-on service notification icon candidates, each
keeping the centred brand gem with a "background service / connected to
servers" motif around it:

- amethyst_service3: orbit ring with three server nodes
- amethyst_service4: two looping sync arrows (running)
- amethyst_service5: hub & spoke to five nodes (connected to relays)
- amethyst_service6: concentric broadcast waves

To compare all candidates on a real device, add a DEBUG-only helper
(ServiceIconPreviewNotifications) that posts one always-on-style ongoing
notification per icon (same channel style, ongoing/silent/low priority),
triggered from MainActivity.onCreate under BuildConfig.DEBUG.

NOTE: the preview harness (ServiceIconPreviewNotifications + the
MainActivity hook) is temporary scaffolding for the icon bake-off and is
meant to be removed once a design is chosen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 15:33:15 +00:00
Vitor PamplonaandGitHub 6b7e541f55 Merge pull request #3380 from vitorpamplona/claude/adoring-babbage-stzzck
Auto-save Cashu wallet on mint add/remove, remove manual key picker
2026-06-26 11:16:48 -04:00
Claude a242411df6 refactor: own the draft note reference inside DraftTagState
The strong reference that keeps a saved draft alive (so LocalCache's weak
reference can't collect it before deletion) was duplicated as a draftNote
field across all eight composer ViewModels, each re-clearing it in
cancel(). The note's lifecycle is 1:1 with the draft tag, so DraftTagState
is its natural owner: it now holds the AddressableNote, exposes held() to
set it, and drops it in rotate() — which every cancel() already calls.

ViewModels now reference draftTag.note / draftTag.held(...) and no longer
carry their own field or reset logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 15:15:42 +00:00
Claude b4d39a7e52 feat: auto-publish Cashu wallet on mint add/remove, drop Save button
The Add/Edit Cashu wallet screen no longer has a Save button. The NIP-60
wallet (kind:17375) and nutzap info (kind:10019) are now published the
instant the first mint is added, and re-published on every later add or
remove — so the list on screen always matches what's on relays.

This removes the confusing two-step flow where a typed/selected mint plus a
chosen key still left Save disabled until the user discovered the "+" button.

The explicit P2PK key picker (auto-generate / paste) is gone from this
screen: a nutzap key is generated automatically on first creation. Advanced
key import/rotation still lives in the settings Danger Zone.

Key safety: publishMints reuses the wallet's existing P2PK key on every
re-publish. The first publish's key is cached in the ViewModel and guarded
by a mutex, so rapid successive adds — firing before the new kind:17375
round-trips back through LocalCache — can't generate a second key and
rotate it, which would orphan inbound nutzaps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VX5wkwXbvoQpadALx1ZVjk
2026-06-26 15:09:20 +00:00
Vitor PamplonaandGitHub 356e1431d8 Merge pull request #3378 from vitorpamplona/fix/embed-ime-selection
Embedded text selection: native-parity IME + selection UI + magnifier, and platform-bug fixes
2026-06-26 11:08:42 -04:00
Vitor PamplonaandGitHub 65f5292f6b Merge pull request #3379 from vitorpamplona/claude/notecompose-zap-split-preview-dgn0js
Show boosted notes as compact preview in activity cards
2026-06-26 11:08:17 -04:00
Claude 50dbbeeecc fix: hold a strong reference to the draft note for reliable deletion
LocalCache.addressables keeps AddressableNotes via WeakReference, so a
saved draft could be garbage-collected between creation and deletion.
The previous existence check (look the draft up by tag before signing)
would then find nothing locally and skip the deletion, leaving an orphan
draft on the relays.

Each composer ViewModel now holds a strong reference to its draft note:
createAndSendDraftIgnoreErrors returns the consumed AddressableNote, and
load()/editFromDraft captures the note when editing an existing draft.
deleteDraftInner takes that held note directly (sourcing the dTag and
relays from it) instead of a tag lookup, so it can always reach the draft
it needs to delete and still signs nothing when there is no draft.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 14:59:44 +00:00
Vitor PamplonaandClaude Opus 4.8 11e1e17655 docs(embed): record session fixes in the native-parity plan
Mark the full-screen round-trip corruption bug FIXED (process-global pauseTimers
+ attached WebView.destroy), and add the no-blink word-select, page-selection-
clears-on-focus, caret-handle-drag (positionChangeIgnoreConsumed), hybrid
word+char extend, and embed app-theme (nightThemedContext) fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:57:36 -04:00
Claude bacb8a57ad feat: add circular-badge service icon as amethyst_service2 option
Keep the original hollow-outline amethyst_service icon and add the new
circular badge (solid disc with the gem punched out of its centre) as a
separate amethyst_service2 drawable, so the always-on notification icon
can be switched between the two without losing either design.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 14:57:22 +00:00
Claude bf2dc03a50 fix: escape apostrophe inside CDATA in fr-rCA strings
The Crowdin-synced values-fr-rCA/strings.xml had unescaped apostrophes inside the
CDATA sections of block_hide_user and report_dialog_block_hide_user_btn, which
aapt2 rejects ("Invalid unicode escape sequence"), breaking resource compilation.
Escape them as \' to match the known-good values-fr translations.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4rrFiWApq8EFpk4fSuTFH
2026-06-26 14:54:09 +00:00
Vitor PamplonaandGitHub f458fdaa8a Merge pull request #3377 from vitorpamplona/claude/amethyst-git-notifications-rqfkux
Add Git pull request event rendering support (NIP-34)
2026-06-26 10:53:30 -04:00
Claude 120c31ac95 feat: always show short preview for posts inside zap activity cards
The lightning, nutzap, and onchain activity cards embed the zapped post via
RenderZappedPost with makeItShort = true. The 2-line compact preview, however,
only triggered when the logged-in user authored the post
(makeItShort && isLoggedUser(author)). When the user is merely a zap-split
beneficiary of someone else's post, that check failed and the post rendered in
full instead of the intended compact preview.

Gate the short preview on the boosted-note flag as well, so any post embedded in
one of these activity cards (the only makeItShort callers that pass
isBoostedNote = true) is always shown as a 2-line preview, regardless of author.
Other makeItShort callers (Report, community post approval, attestation, reply
composition, compose screens) all use isQuotedNote instead, so their behavior is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4rrFiWApq8EFpk4fSuTFH
2026-06-26 14:49:47 +00:00
Vitor PamplonaandClaude Opus 4.8 43aebcca63 fix(embed): make embedded + full-screen WebViews follow the app theme
The embedded in-app browser and napplet/nSite surfaces rendered web content in
the device theme, ignoring the app's DARK/LIGHT preference — a site with dark
support stayed light when the app was dark (and vice versa). The full-screen
activities had the same latent gap (they followed the device, not the app).

Root cause: WebView's dark decision (prefers-color-scheme via algorithmic
darkening) reads the context's THEME (?android:attr/isLightTheme), not just the
Configuration uiMode. The off-window SurfaceControlViewHost surface context
carries neither the host window's theme nor its night mode, so the renderer came
up light. The old applyNightMode() used UiModeManager.setNightMode — a
permission-gated no-op — so the theme never reached the WebView at all.

Fix: build every embed/host WebView from nightThemedContext() — a
ContextThemeWrapper over a forced-night/day Configuration with a DayNight theme,
so the theme's isLightTheme resolves from the app's resolved theme. Shared in
EmbedWebViewTheme.kt; used by NappletBrowserService, NappletHostService,
NappletBrowserActivity, and NappletHostActivity. Removed the dead applyNightMode
no-op from all four. (Verified on device with a throwaway SurfaceControlViewHost
repro: config-only context does NOT work; setForceDark is a no-op at targetSdk
37; setApplicationNightMode does nothing; the DayNight ContextThemeWrapper is
what flips the renderer, even across the cross-process embedded surface.)

Device-verified: all four surfaces follow the app theme even when it differs
from the device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 10:49:44 -04:00
Claude 3c041d2915 feat: modern git cards for pull requests, plus issue/patch polish
NIP-34 pull requests (kind 1618) and pull-request updates (kind 1619)
previously had no renderer and fell through to the plain text-note
branch, so a PR notification opened onto an unstyled markdown blob with
none of its structured data shown. Add dedicated cards that reuse the
existing patch/issue card vocabulary (bordered container, type chip,
status pill, embedded repository header) and surface the PR-specific
metadata the event carries:

- Pull Request card: "Pull Request" chip with a merge glyph, status
  pill, subject title, branch name, current commit, merge base, and
  clone-URL download rows.
- PR Update card: "PR Update" chip, repository header, new commit /
  merge base, clone URLs, and an explanatory line (updates carry no
  body content).

While here, modernize the existing cards consistently:

- Factor the shared markdown body, metadata row, and subject title into
  reusable composables (GitMarkdownBody / GitMetaRow / GitSubjectTitle).
- Show the issue subject as a proper title. The old code cast the event
  to TextNoteEvent to read the subject, which always returned null
  (GitIssueEvent is not a TextNoteEvent), so issue subjects were never
  displayed; read it from GitIssueEvent.subject() instead.
- Render the patch commit as an iconed metadata row.

Wire the two new kinds into NoteCompose and the thread detail view, add
the CallMerge / Commit / AltRoute Material symbols (font subset
regenerated), and add the new string resources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wCXL6btUeZZmSP1TCYkKh
2026-06-26 13:39:05 +00:00
Claude df5361d220 fix: don't sign a draft deletion when no draft exists
deleteDraftInner() unconditionally signed a deleted-draft wrap event plus
a deletion event, even when no draft for that tag existed. With an
external NIP-55/NIP-46 signer, clearing a composer to blank could then
prompt for a signature even when "Automatically create drafts" is off,
since the blank-text branch of sendDraftSync() always calls delete.

Skip both signatures when the draft does not exist in the cache. The
existence lookup reuses the addressable note already fetched for relay
hints, so it adds no extra work, and real drafts are still deleted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 13:38:20 +00:00
Claude 7234324332 fix: escape apostrophes in fr-rCA CDATA strings
Two French-Canadian CDATA string resources carried a raw apostrophe
(`l'utilisateur`), which aapt2 rejects with "Invalid unicode escape
sequence", breaking every Android resource merge (merge*Resources) and
therefore the whole Android build. Escape them as `\'` like the other
apostrophes in the file.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wCXL6btUeZZmSP1TCYkKh
2026-06-26 13:09:47 +00:00
Claude 173a422d86 fix: notify on NIP-34 pull requests (kind 1618/1619)
New pull request events (GitPullRequestEvent, kind 1618) and pull
request updates (GitPullRequestUpdateEvent, kind 1619) were never wired
into the notification pipeline, which only handled patches (1617) and
issues (1621). As a result repo maintainers received no notification
when a PR was opened against their repository, even though the PR event
p-tags the repo owner.

Add both kinds in all four places that gate notifications:

- FilterNotificationsToPubkey: request the kinds from relays so the
  notification subscription actually pulls PR events.
- NotificationFeedFilter.NOTIFICATION_KINDS: let cached PR events through
  to the in-app Notifications tab.
- NotificationFeedFilter.tagsAnEventByUser: treat PR/PR-update as
  notifiable (mirrors the existing patch/issue handling).
- EventNotificationConsumer: dispatch PR/PR-update via notifyMention so
  push notifications fire.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wCXL6btUeZZmSP1TCYkKh
2026-06-26 13:00:39 +00:00
Claude fa12576a8c fix: respect "Automatically create drafts" setting in comment composer
CommentPostViewModel.sendDraftSync() unconditionally signed and
published a draft event whenever the user typed in a comment, ignoring
the "Automatically create drafts" setting. Every other composer
ViewModel guards this path with
accountViewModel.settings.automaticallyCreateDrafts(); add the same
check here so disabling the setting stops generic draft events from
being signed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016aj4ajZ4uQ58Bqts7riJ5i
2026-06-26 12:29:37 +00:00
Vitor PamplonaandGitHub 69e9590fe7 Merge pull request #3374 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-26 08:25:50 -04:00
Claude 27191c3a36 fix: enlarge gem in service notification icon by 10%
The punched-out gem now spans ~57% of the disc diameter (was ~52%),
giving it slightly more presence within the circular badge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kvjst6qVHuQ8rKD3LtvTzi
2026-06-26 02:06:49 +00:00
vitorpamplonaandgithub-actions[bot] f5bb04b643 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-26 01:34:37 +00:00
Vitor PamplonaandGitHub f2cc46641c Merge pull request #3376 from vitorpamplona/claude/keen-franklin-h4kah5
feat: query zapstore's relay in the global Apps feed and keep its filter specific
2026-06-25 21:32:20 -04:00
Vitor PamplonaandClaude Opus 4.8 6995082334 fix(embed): full-screen round-trip no longer corrupts embedded WebViews
Opening an embedded page full-screen and returning left every embedded WebView
in the :napplet process broken: dead DNS (ERR_NAME_NOT_RESOLVED), DOM reads
returning empty (a field that visibly shows text reports value==""), dead
text-selection highlight, and broken IME (caret stuck at 0, can't delete).

Two process-global defects in the full-screen hosts were corrupting the shared
WebView state the embedded surfaces rely on:

- pauseTimers()/resumeTimers() are PROCESS-GLOBAL — they pause/resume JS,
  layout and parsing timers for every WebView in the process. The full-screen
  activities (and the napplet embed pause/resume path) called them on their own
  lifecycle, so returning from full-screen froze the embedded surfaces, which
  have no resume of their own. Replaced with per-WebView onPause()/onResume()
  (which pause only that surface's JS/DOM — still satisfies the napplet
  background-security goal). No process-global timer calls remain.
- WebView.destroy() was called while the WebView was still attached to the
  window, which corrupts the shared multiprocess renderer. Detach (removeView)
  and stopLoading() before destroy() in both full-screen activities.

This also resolves the long-standing "selection highlight dead after a
full-screen excursion" blocker — same root cause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00
Vitor PamplonaandClaude Opus 4.8 aa520c361f feat(embed): hybrid word+char granularity for in-field selection handles
Completes parity #5. `fieldExtend` kept a pure word-snap, so the in-field
start/end handles couldn't be fine-tuned to a single character. Now it keeps
per-drag state (reset on a >250ms gap or edge switch) and matches native
`Editor` word-selection drags: the gesture baselines at the current selection
edge, sweeping past that word's far boundary snaps to the next whole word
(never stopping mid-gap), and moving within / back from the furthest-reached
word gives character precision. Symmetric for both handles. Page-text extend
stays character-granular.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00
Vitor PamplonaandClaude Opus 4.8 dec1e0bec1 feat(embed): selection loupe + native-parity fixes for embedded text selection
Builds out host-drawn text selection for embedded napplet/nsite/browser
surfaces toward native parity, and fixes the bugs found while exercising it.

- Magnifier loupe (#4): EmbeddedMagnifier + provider-side pixel capture
  (EmbeddedMagnifierProbe) shipped over IPC for both embed paths; the
  caret/selection handles drive it via OnMagnify.
- SelectionUiState: single source of truth for the overlay show/hide rules
  (insertion caret / in-field range / page-text range + dragging/scrolling).
- EmbeddedSelectionDrag: suspends the nav drawer's edge swipe while a handle
  is dragged (auto-scroll #9).

Bug fixes:
- No more overlay blink on word-select: the shim's selection-reveal scrolls
  (a textarea auto-scrolling to show a forming/re-asserted range) no longer
  trip the hide-on-scroll path, and the hide self-heals instead of being
  re-armed indefinitely.
- RemoteImeView debounces the range-lost signal so a transient collapse that
  gets re-asserted doesn't flicker the handles/toolbar.
- Focusing a field clears any page-text selection (shim + host), so the stale
  page handles/Copy bar no longer linger above — and stop stealing drags from —
  the field overlays; also cancels any in-flight scroll-hide on focus.
- Caret insertion-handle drag now actually moves the caret: read the pointer
  delta with positionChangeIgnoreConsumed() before consuming, so the value
  isn't zeroed by our own consume (or the sandbox surface consuming the move).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00
Vitor PamplonaandClaude Opus 4.8 8232cd50a3 test(embed): add IME/selection test harness + document how to run it
tools/ime-test/index.html is a single-page input+textarea harness with an
on-page log timestamping focus/selection/input/composition events, paint
latency, long-tasks, and main-thread blocks — the instrumentation used to pin
the erase / caret-jump / first-letter-freeze bugs and what we'll use to profile
the magnifier. README documents serving it (python http.server on 8765,
10.0.2.2 for emulator / adb reverse for USB) and opening it as an embedded tab
via the in-app browser address bar. Plan gets a matching "How to test" section.

Dev tool only — nothing under tools/ ships, so the [ImeDiag] strings stay out of src/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00
Vitor PamplonaandClaude Opus 4.8 d351376e51 docs(embed): plan for native-parity text selection in embedded surfaces
Inventory native Android text-field features (insertion/selection handles,
floating toolbar, magnifier, smart selection, etc.) with activation rules and
our current coverage; prioritize the magnifier and a centralized SelectionUiState.
Document the open full-screen round-trip bug that kills highlight paint across
all :napplet embeds, what was already ruled out, and the next hypotheses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00
Vitor PamplonaandClaude Opus 4.8 baddd5b3bd fix(embed): IME typing + host-drawn text selection for embedded surfaces
Embedded WebView surfaces (:napplet process, SurfaceControlViewHost) can't
host the soft keyboard or present Chrome's own selection UI, so editing and
selection are relayed to the main process. This lands the working set of that
relay:

- shim.js: fix React-controlled input erase by writing through the native
  HTMLInputElement/HTMLTextAreaElement value setter (so React's value tracker
  stays in sync); make the host authoritative for selection re-assert (the
  editable selectionchange handler only mirrors); report field/caret geometry
  and page-text selection geometry; add pageExtend + caret coords (border-width
  corrected) for drag-to-extend and the insertion handle.
- RemoteImeView: land caret where tapped on focus (requestFocus before applying
  remote state); host-authoritative selection re-assert within a time window;
  setText only when text actually changed; wire copy/cut/paste/select-all and
  edit callbacks.
- EmbeddedTabLayer: stop resizing the surface on IME show (removes the ~1s
  first-letter freeze); draw the selection overlay — toolbar, teardrop
  selection handles, and the insertion (cursor) handle.
- EmbeddedImeBridge / Embedded{Napplet,Browser}Controller: carry selection +
  caret geometry and the page-selection event across the Messenger channel.

Known limitation (not fixed here): after a field's page is opened in its own
full-screen activity and the user returns, the selection-highlight paint stays
off across all embedded surfaces. DOM selection, the toolbar, and copy still
work — only the native highlight is gone. This is a WebView/Chromium behavior
in off-window surfaces and is not reachable from the app layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 21:25:55 -04:00