Commit Graph
15892 Commits
Author SHA1 Message Date
Claude 3cc0ce09d4 feat: show website favicon and domain in all browser-connected app views
Web app entries (browser:https://...) now display:
- The captured favicon from BrowserIconRegistry (same source as the
  bottom-nav favourite website icon) in ConnectedAppsScreen,
  ConnectedAppDetailScreen, and all three permission/consent dialogs
- The domain name (host) as the title instead of the full URL, via
  OmniboxInput.hostOf() in loadDetailState, NappletConsentSummary,
  buildSignerConsentInfo, and buildConnectInfo
- A globe icon (FavoriteApp.WebApp) instead of the grid icon
  (FavoriteApp.NostrApp) in all consent dialog headers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-28 15:41:27 +00:00
Claude 6f9bf33595 fix: show globe icon and domain for browser-connected web apps
The permission ledger stores browser-visited origins as `browser:<url>`
where "browser" is a sentinel non-pubkey. All previous code treated every
entry as a napplet, causing NPub.create("browser") to fail and
LocalCache.checkGetOrCreateAddressableNote to return null for every entry
-- so icons and titles never resolved.

Now ConnectedAppCard splits on author == "browser": web-app entries get a
globe icon (FavoriteApp.WebApp), the domain extracted from the URL as
title, and no npub row. Napplet entries (real hex pubkeys) continue using
the reactive manifest lookup. The relay subscription now also filters out
"browser" from the authors set so no invalid pubkey is sent to relays.

ConnectedAppDetailScreen receives the same fix in AppIdentityHeader, using
FavoriteApp.WebApp for browser entries so the globe icon appears there too.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-28 15:17:04 +00:00
Claude 0d51dff41a feat: live relay subscription for connected-apps manifest screen
Replaces the one-shot fetchAll with a ComposeSubscriptionManager that
holds an open relay subscription to NIP-5D manifests (kinds 15129/35129)
for exactly the set of authors stored in the permission ledger, while
ConnectedAppsScreen is in composition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-28 14:03:17 +00:00
Claude 05b3e3e25e fix: make ConnectedAppsScreen fully reactive for title and icon
The previous approach baked title/iconUrl into ConnectedAppEntry at
load time (a snapshot) and passed pubkey:identifier to
rememberNappletIconModel which expects kind:pubkey:dtag — so both
title and icon never updated from LocalCache.

Now:
- ConnectedAppEntry only holds coordinate + signerPolicy
- Each ConnectedAppCard builds the full kind:pubkey:dtag coordinate
  (kind 15129 for root napplets with empty identifier, 35129 for named)
- rememberNappletManifest observes the live AddressableNote in
  LocalCache so title/iconUrl update reactively as manifests arrive
- rememberNappletIconModel receives the correct full coordinate so
  blossom blob icons load exactly like FavoriteAppsScreen
- The relay fetch now covers all connected apps (not just those with
  missing iconUrl) so manifests arrive promptly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-28 01:09:00 +00:00
Claude e0a6ed5ffc fix: i18n kind names in signer consent; live icons in connected apps list
- Extract kindDisplayName() to KindDisplayName.kt and add kindNameFor()
  helper that picks the translated string resource when available,
  falls back to KindNames English map, then "k<number>"
- NostrSignerOpLabels: use kindNameFor() so "sign for Notes (kind: 1)"
  is translated instead of always English
- ConnectedAppsScreen: wire rememberNappletIconModel so the card icons
  load from blossom just like FavoriteAppsScreen does
- Remove the now-duplicate kindDisplayName() definition from
  RelayInformationScreen.kt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-28 00:01:02 +00:00
Claude f2fbe260d5 feat: show kind name in signer consent label; fix icon race in ConnectedAppsScreen
- NostrSignerOp.SignKind label now renders "sign for <Kind Name> (kind: N)"
  using KindNames.nameFor(), falling back to "sign kind N event" for unknowns
- Fix ConnectedAppsScreen race: merge two LaunchedEffects into one so
  items is set before observeEvents fires and before the relay fetch runs;
  previously both could find items==null and return early, leaving icons blank
2026-06-27 23:17:56 +00:00
Claude 7fd0741778 fix: center-align text in deny/block OutlinedButtons
Remove the Modifier.fillMaxWidth() + TextAlign.Start that was forcing
button labels to the left edge; buttons now use the default centered layout.
2026-06-27 23:09:31 +00:00
Claude 2e81a71f19 refactor(napplet): replace newEventBundles with LocalCache.observeEvents for manifest updates
LocalCache.observeEvents<Event>(filter) handles both the initial snapshot and
subsequent insertions via the observables registry, replacing the manual
bundle-scan over newEventBundles. Fewer moving parts and the filter is scoped
to manifest kinds rather than scanning every arriving event bundle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:53 +00:00
Claude debe3c3a48 feat(napplet): rich app cards with dynamic manifest loading in Connected Apps list
- Add authorPubKey (npub) to ConnectedAppEntry and show it in each card below the
  domain, so users can verify who published the napplet/nsite
- Increase icon to 48dp and use surfaceVariant card background to match the detail
  screen's AppIdentityHeader style
- After the initial cache-based load, subscribe to LocalCache.live.newEventBundles
  and re-resolve metadata (title + icon) for all entries whenever a manifest event
  (kind 15129 / 35129) arrives — handles manifests delivered by any relay subscription
- For apps whose icon is not yet cached, issue a one-shot relay fetch (kinds 15129/35129
  for the relevant author set) via account.client.fetchAll; inject results into
  LocalCache.justConsume so the newEventBundles observer picks them up and updates
  the list without a full reload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:53 +00:00
Claude 1086f4cf5e fix(napplet): unify DataStore cache key to file path, fixing connected-apps crash
allPolicies() keyed the LargeCache on the filename ("nsp_HASH") while storeFor()
keyed it on the coordinate string. Both point at the same .preferences_pb file,
so getOrCreate created two live DataStore instances for one file —
DataStore's own singleton guard then threw IllegalStateException.

Fix: use file.absolutePath as the cache key in both code paths so the second
call always returns the already-open DataStore instance instead of creating a
new one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:53 +00:00
Claude 94ac370379 fix(napplet): consistent button styling, fix DataStore crash, icon loading, dead-code cleanup
- Make all four consent actions (Always Allow / Allow Once / Deny Once / Always Deny)
  full-width OutlinedButton/Button so they are visually consistent; remove TextButton
  with left-aligned text that clashed with the centered primary buttons
- Move domain/coordinate label into the header Column directly below the subtitle so
  the URL is contextually grouped with the app identity rather than floating near buttons
- Fix DataStore multiple-instances crash: add nappletPermissionStore and signerPermissionStore
  as lazy singletons in AppModules; warm them up on IO thread to avoid StrictMode
  DiskReadViolation; update NappletBrokerService and connected-apps screens to use the
  shared singletons instead of creating independent instances
- Propagate iconUrl through NappletConsentInfo / NappletSignerConsentInfo / NappletConnectInfo
  data classes; resolve napplet metadata (title + icon) in the broker-side builders
  (NappletConsentSummary, NostrSignerOpLabels) so dialogs receive it ready to display
- Replace manual buildEventJson (org.json) with JacksonMapper.toJsonPretty(EventTemplate)
  in NostrSignerOpLabels; add toJsonPretty(EventTemplate<*>) overload to JacksonMapper
- Delete dead NappletPermissionsScreen.kt and NappletSignerPermissionsScreen.kt (never
  navigated to); remove Route.NappletPermissions and its composable registration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:52 +00:00
Claude 840237fa72 fix(napplet): convert capability consent to floating dialog, match consent style
Replace the AlertDialog with the same Surface(extraLarge)/Dialog pattern
used by the signer consent and connect dialogs. Shows app icon + name +
capability category in the centered header, operation detail (with any
content preview) in a selectable surfaceVariant box, and the same button
hierarchy: Always allow (Button, primary) / Allow once (FilledTonalButton)
then Never allow / Not now as left-aligned TextButtons below a divider.
When the capability is per-use only (payments), Allow once is promoted
to the primary Button.
2026-06-27 22:58:52 +00:00
Claude 9dcfc3a0ed fix(napplet): convert connect screen to floating dialog, match consent style
Replace the full-screen semi-transparent overlay with a floating card
Dialog (same Surface/shape/elevation as the signer consent dialog) so
both permission prompts look consistent. Header now shows the app icon,
name, and "wants to connect to your Nostr account" subtitle instead of
the generic "Connect to Nostr" headline. Block button style matches the
deny row in the signer consent.
2026-06-27 22:58:52 +00:00
Claude af2fcc46b6 feat(napplet): promote Always Allow to primary action in signer consent
Move AllowForOp to the primary Button (filled) since always allowing the
operation is the preferred choice, with AllowOnce as the secondary
FilledTonalButton. The time-bound and allow-all options stay in "More
options", keeping the nuclear allow-all less discoverable.
2026-06-27 22:58:52 +00:00
Claude c7566f2685 fix(napplet): center connect dialog, fix label color and domain display
- Wrap content in a scrollable Box with Alignment.Center so the dialog
  is vertically centered instead of stuck at the top
- Explicitly set onSurface color on the PolicyOption label so it stays
  visible regardless of Surface background tint
- Fix buildConnectInfo to show the napplet identifier (e.g. "browser")
  in the block button instead of the raw coordinate prefix (pubkey)
2026-06-27 22:58:52 +00:00
Claude 343263cb2c fix: use CommonsR for napplet_untitled after resource move to commons
The string moved to commons/src/androidMain/res/values/strings.xml
in the upstream merge; update the two remaining call sites.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:52 +00:00
Claude d05247541d refactor: extract shared napplet abstractions, remove what-comments
- Extract resolveNappletMeta() to NappletManifestLookup.kt, replacing
  three private copies of the same manifest lookup across
  ConnectedAppsScreen, ConnectedAppDetailScreen, NappletPermissionsScreen,
  and NappletSignerConsentActivity.
- Extract PolicyCard composable to PolicyCard.kt, shared between
  ConnectedAppDetailScreen and RelayAuthSettingsScreen (was duplicated).
- Extract NappletCapability.symbol() to NappletCapabilityExt.kt, shared
  between ConnectedAppDetailScreen and NappletPermissionsScreen.
- Drop what-comments on kind 1/6/7 lines in NostrSignerPermissionLedger.
- Reword TrustedRelayListState stateIn comment to note private-tag absence
  on first boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:52 +00:00
Claude 330379e53f fix(napplets): address four code-review findings before merge
- Cancel on first-connect dialog now suppresses re-prompting for the
  rest of the session (sessionCancelled set under signerConsentLock)
  instead of showing the dialog on every subsequent request.
- PARANOID signer policy no longer silently bulk-grants ALLOW_ALWAYS
  for all capabilities; the capability ledger is left empty so each
  capability prompts individually, matching user intent.
- NostrSignerOp.Decrypt default changed from ALLOW → ASK in
  reasonableDecision(); the branch is currently unreachable
  (toSignerOp() never produces Decrypt) but ASK is the safer default
  if a decrypt request type is added in future.
- TrustedRelayListState seeds its StateFlow from the synchronously
  available cached relay set, eliminating a startup window where
  IF_IN_MY_LIST incorrectly denied auth to relays in the user's list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:52 +00:00
Claude 52f1b8723c feat(napplets): require Amethyst consent for all signer types, auto-approve encrypt/decrypt
Remove the signer self-gating bypass that allowed external (Amber/NIP-55)
and remote (NIP-46) signers to skip Amethyst's per-napplet consent UI.
All signer types now go through Amethyst's consent dialogs first; the
external signer then adds its own approval on top (double-prompting).
This lets users differentiate signing requests by app inside the external
signer, since Amethyst itself is the requesting app.

Also expand the REASONABLE policy to auto-approve Encrypt and Decrypt
operations, matching the intent that common/private-key operations that
apps routinely need are pre-approved at the "reasonable" trust level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 2786c5b683 fix(relayauth): expand IF_IN_MY_LIST to all live relay lists minus blocked
Replace the localRelayServers-only check with account.trustedRelays
(nip65, private outbox, local, dm, search, indexer, proxy, trusted,
broadcast relay lists combined) minus account.blockedRelayList. Uses
normalizeRelayUrlOrNull for consistent URL comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 162ee51955 feat(ui): modernize napplet and relay auth permission screens
- ConnectedAppDetailScreen: replace emoji policy icons (❤👍🕶) with
  themed MaterialSymbols (Favorite/Shield/Lock); show domain instead of
  raw coordinate in AppIdentityHeader
- ConnectedAppsScreen: remove redundant Column wrapper around SuggestionChip
- NappletSignerConsentActivity: add centered FavoriteAppIcon header with
  title + description; promote "Allow once" to FilledTonalButton; collapse
  time-based/granular grants behind a "More options" toggle; resolve app
  icon from cache using coordinate
- RelayAuthSettingsScreen: replace plain radio buttons with styled PolicyCard
  composable (bordered card, icon, check mark); wrap per-relay overrides in
  surfaceVariant Surface with dividers; fix duplicate if/if → if/else;
  add TextOverflow.Ellipsis to URL text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 9a117d6166 feat(napplets): show app icon in Connected Apps list and detail
Replace the static placeholder glyph with the napplet manifest's
icon() URL loaded via FavoriteAppIcon (Coil AsyncImage + glyph
fallback). Both ConnectedAppsScreen and ConnectedAppDetailScreen now
resolve iconUrl alongside title from the event cache and display the
real app icon when available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude d57c3f71db fix: update quartz auth tests to match new two-arg signWithAllLoggedInUsers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 0e6e7901c2 fix: update KtorRelayTest to match new signWithAllLoggedInUsers signature
The lambda now receives (relay, template) after RelayAuthenticator was
updated to pass the relay URL for per-relay auth policy checks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 328790ac9f feat: time-bound signer grants, last-used tracking, and relay AUTH settings (NIP-42)
- Add AllowForSession and AllowUntil(expiresAt) signer grant types so users
  can grant temporary access (session, 24h, 30d) from the consent dialog
- Track per-app lastUsed timestamp in NostrSignerPermissionStore and update
  it on every granted signing operation
- Auto-expire timed grants: decide() clears expired op decisions before
  returning, so no background sweep is needed
- Add NappletBroker.sessionAllows in-memory set for session grants (cleared
  on broker destroy, never persisted)
- Implement full NIP-42 relay auth policy system:
  - RelayAuthPolicy enum (ALWAYS / NEVER / IF_IN_MY_LIST) stored in
    AccountSettings and persisted in LocalPreferences
  - RelayAuthDecision (ALLOW / DENY) per-relay overrides in DataStore
  - RelayAuthPermissionLedger combining global policy + per-relay overrides
  - DataStoreRelayAuthPermissionStore writing to relay_auth.preferences_pb
- Wire relay auth into AuthCoordinator: subscribeLedger/unsubscribeLedger
  lets each logged-in account contribute its own policy; signWithAllLoggedInUsers
  now receives the relay URL so it can check the ledger before signing
- Update RelayAuthenticator (quartz) to pass relay URL in the signing lambda
- Add RelayAuthSubscription composable that subscribes both the account and
  its ledger when a screen is active
- Add RelayAuthSettingsScreen: global policy radio picker + per-relay
  override list with toggle and remove; reachable from Settings
- Add Route.RelayAuthSettings, AppNavigation wiring, and SettingsCatalog entry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude 77dacd9e68 feat: add Connected Apps settings screen with per-app permission detail
Adds a unified "Connected Apps" entry to Account Settings that surfaces
all web apps that have connected to the user's Nostr key (both capability
grants and signer trust levels) in one place.

- Route.ConnectedApps (list) + Route.ConnectedAppDetail(coordinate) (detail)
- ConnectedAppsScreen: merges NappletPermissionLedger + NostrSignerPermissionLedger,
  shows one card per app sorted alphabetically with trust-level chip
- ConnectedAppDetailScreen: identity header, editable trust-level picker
  (Full Trust / Reasonable / Paranoid), op override rows with revoke, capability
  switches, and a "Forget this app" button that clears all permissions
- Settings catalog entry under Account (Apps symbol, keyword-searchable)
- NappletsTopBar Tune icon now navigates to ConnectedApps instead of
  the old capability-only NappletPermissions screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:51 +00:00
Claude e03fef36e4 refactor: replace hand-rolled JSON builder with org.json.JSONObject
buildEventJson was manually escaping quotes, newlines, and backslashes
which is fragile. org.json.JSONObject.toString(2) handles all escaping
correctly and produces the same pretty-printed output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:50 +00:00
Claude e5763b947c fix: polish napplet signer permission UIs
- NappletSignerConsentActivity: replace AlertDialog (broken 5-button
  layout) with a custom Dialog + Surface using heightIn + verticalScroll;
  color-coded allow (primary) / deny (error) action rows; monospace
  "See more" toggle that reveals full raw event JSON with SelectionContainer
  so users can inspect and copy the data being signed/encrypted
- NappletConnectActivity: wrap column in verticalScroll so the trust-level
  options are not clipped on small screens or large font sizes
- NappletSignerPermissionsScreen: show localized op labels ("sign kind 1
  event") and decision labels ("Allow"/"Ask"/"Deny") instead of raw key
  strings and enum names; fix per-op delete touch target to 48dp (M3 min)
- NappletSignerConsentInfo: add rawData field carrying full event JSON for
  sign/encrypt or decrypted plaintext for future decrypt operations
- NostrSignerOpLabels: populate rawData; add buildEventJson helper
- strings: napplet_op_decrypt → "read your private messages" (the consent
  is to expose already-decrypted content, not to perform decryption);
  add napplet_consent_wants_to, see_more/see_less, decision labels

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:50 +00:00
Claude b48cad67e5 feat: add Nostr signer permission system for napplets/nsites
Implements per-app permission management for the internal nsec signer when
webapps/napplets/nsites connect via Amethyst's built-in key:

- Three trust levels on first connect (FULL_TRUST, REASONABLE, PARANOID)
  with a UI dialog (NappletConnectActivity) matching the design spec
- Per-operation consent dialogs (NappletSignerConsentActivity) for
  sign-kind/encrypt/decrypt with Allow once, Don't ask again, Deny options
- Per-app DataStore storage (DataStoreNostrSignerPermissionStore) using
  SHA-256-hashed filenames so 1000s of apps don't bloat a single file
- NostrSignerPermissionLedger applies policy decisions: REASONABLE
  auto-allows kinds 1/6/7; FULL_TRUST auto-allows all non-payment ops
- NappletBroker extended with first-connect gate and per-op signer gate,
  serialized by a dedicated signerConsentLock mutex
- Permission management screen (NappletSignerPermissionsScreen) to review
  and revoke stored per-app signer permissions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hTFpoExYYLYEGGtXBx6ZT
2026-06-27 22:58:50 +00:00
Vitor PamplonaandClaude Opus 4.8 82f40e0ed1 Merge PR: Fix embedded WebView theming (dark-page corruption + live theme switch)
Merges nostr proposal 81ee3e7c into main:
- fix(napplet): drop algorithmic darkening so dark-by-default sites (e.g.
  ditto.pub) aren't corrupted — it collided with nightThemedContext and inverted
  their nav bars to light. prefers-color-scheme: dark still works without it.
- feat(embed): follow the app theme live in embedded web tabs by rebuilding the
  warm sessions on a DARK/LIGHT flip, instead of requiring an app restart.

Device-verified on ditto.pub + brainstorm.nosfabrica.com, Dark<->Light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:47:05 -04:00
Vitor PamplonaandClaude Opus 4.8 925c5e454a feat(embed): follow the app theme live in embedded web tabs
An embedded tab's WebView resolves its theme from the context it is built with
(nightThemedContext), once, at construction — a runtime config change does not
re-flip the renderer — so a live app-theme switch never reached an already-warm
surface; it took a full app restart.

Watch the resolved DARK/LIGHT theme and, on a real flip, rebuild the warm
sessions in the new theme:
- EmbeddedTabHost.themeEpoch + rebuildAllForTheme() tears down the warm
  controllers but keeps activeId, so the visible tab re-activates the instant its
  screen re-acquires (no blanked-out surface).
- EmbeddedTabThemeWatcher (mounted by AppNavigation next to the tab layer)
  collects uiPrefs.theme + isSystemInDarkTheme() and triggers the rebuild.
- WebAppScreen / NostrAppScreen key their controller on themeEpoch (NostrApp also
  re-mints its launch params); the preloader re-warms off-screen tabs; the tab
  layer keys each surface on (id, controller) so a rebuilt session gets a fresh
  SandboxedSdkView.

The page reloads in the new theme (unavoidable — the theme is fixed at WebView
construction). Device-verified Dark<->Light with no app restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:43:05 -04:00
Vitor PamplonaandClaude Opus 4.8 c6575a0888 fix(napplet): drop algorithmic darkening so dark-by-default sites aren't corrupted
setAlgorithmicDarkeningAllowed(true) was added to force-darken pages that don't
implement prefers-color-scheme. Combined with nightThemedContext (which forces the
embed WebView's isLightTheme=false), it now also runs on pages that are ALREADY
dark but don't declare CSS color-scheme support — e.g. ditto.pub, which ships
<html class="dark"> by default — and algorithmically inverts their nav bars to
light, leaving "dark content, light bars".

prefers-color-scheme: dark is driven by isLightTheme (nightThemedContext)
INDEPENDENTLY of algorithmic darkening — device-verified: embedded pages still
report prefersDark=true with darkening off — so dropping it keeps real dark-aware
sites dark while no longer corrupting dark-by-default ones. The trade-off (a site
with no dark mode of its own renders light instead of being force-inverted)
matches how a real mobile browser behaves.

Removed from all four embed/host WebView configs (browser + nsite/napplet,
embedded + full-screen) along with the now-unused WebSettingsCompat import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 18:43:05 -04:00
Vitor PamplonaandGitHub 4b6e4be942 Merge pull request #3413 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-27 17:46:21 -04:00
vitorpamplonaandgithub-actions[bot] 9792c5c75e chore: sync Crowdin translations and seed translator npub placeholders 2026-06-27 21:03:45 +00:00
Vitor PamplonaandGitHub 6ef7d21132 Merge pull request #3414 from vitorpamplona/claude/search-bar-transparency-75as8b
Add surface background color to SearchBar Column
2026-06-27 17:01:48 -04:00
Vitor PamplonaandClaude Opus 4.8 0e36d416a6 Merge PR: fix(video) — drop errored players from warm pool, crop blurhash to true aspect
Merges nostr proposal 6650c40b into main:
- Square blurhash placeholder: render with ContentScale.Crop when the imeta dim
  is known, so it fills the correctly-sized box instead of letterboxing a
  component-grid-square bitmap inside a taller portrait box.
- "Can't play this video" flash: never return a warm-pooled ExoPlayer carrying a
  stale PlaybackException — releasePlayer drops one that errored before pooling,
  acquirePlayer drops one whose decoder died asynchronously while warm.
- Adds playback-error lifecycle logging used to trace both issues from logcat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:56:34 -04:00
Vitor PamplonaandClaude Opus 4.8 9acb53d574 fix(video): drop errored players from warm pool and crop blurhash to true aspect
Two inline-video bugs surfaced by a portrait Damus post (imeta dim 720x1280, a
rotated H.264 file) that showed a square blurhash in a too-tall box and flashed
"Can't play this video":

- Square blurhash placeholder: the placeholder bitmap is decoded at the
  blurhash's DCT component-grid aspect (e.g. a 5x5 grid -> a square bitmap), not
  the real media shape. When the true ratio is known (from imeta dim) the box is
  already sized correctly, so render the placeholder with ContentScale.Crop to
  fill it instead of letting FillWidth letterbox a square inside the taller
  portrait box.

- "Can't play this video" flash: a warm-pooled ExoPlayer could be handed back
  still carrying a stale PlaybackException. releasePlayer now drops a player that
  errored before being pooled; acquirePlayer drops one whose decoder died
  asynchronously while it sat warm (surface reclaim / codec loss). Either way a
  clean cold/fresh player is used and the stale error never reaches a controller.

Also adds playback-error lifecycle logging (PlaybackError tag with a flattened
cause chain, a live-controller counter, and cold-load + acquire-time
stale-error markers) that made both issues traceable from logcat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:53:34 -04:00
Claude 393ff9ba8b fix: make search filter bar opaque
The top bar on the Search screen (search field + filter row with the 3
scope buttons) had no background, so the feed scrolled visibly behind
the segmented buttons and the gaps in the bar.

Apply the theme surface color to the SearchBar Column, before
statusBarsPadding() so the status-bar inset is filled too, matching the
default Material3 top-app-bar container color used elsewhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkfRpnNyeeo3AVEaX71oRX
2026-06-27 20:47:07 +00:00
Vitor PamplonaandGitHub 3adce5caf8 Merge pull request #3412 from vitorpamplona/claude/app-ui-theme-customization-evwwkb
Add customizable accent colors, fonts, and font sizes
2026-06-27 16:00:11 -04:00
Vitor PamplonaandGitHub 5f8ce0e692 Merge pull request #3411 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-27 16:00:03 -04:00
vitorpamplonaandgithub-actions[bot] ac255d5cc6 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-27 19:59:26 +00:00
Vitor PamplonaandGitHub e81d76e3f3 Merge pull request #3410 from vitorpamplona/claude/nsites-napplets-preload-iwhh1j
Add loading progress bar and error logging to napplet/browser
2026-06-27 15:57:28 -04:00
Claude 373aa7d740 perf: keep ColorScheme.isLight O(1) after accent-color change
isLight fans out to hundreds of themed-color getters on hot note/chat/feed
render paths. The accent-color work had switched it to background.luminance(),
which adds per-call gamma math. Since the accent never touches background
(only primary/secondary) and the dark palette's background is exactly
Color.Black, a single reference comparison is just as accent-robust and
restores the original constant-time cost.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REGsru6cnm6wUzqm12Rh2d
2026-06-27 19:56:53 +00:00
Vitor PamplonaandGitHub 0ed15be975 Merge pull request #3408 from vitorpamplona/claude/nsite-tor-warning-removal-hp52fw
Remove network routing confirmation dialog from napplet host
2026-06-27 15:55:17 -04:00
Vitor PamplonaandGitHub fb357937b8 Merge pull request #3409 from vitorpamplona/claude/default-webclient-list-3sm1ne
Browser: add Discover section with hardcoded web apps & followed nSites/nApplets
2026-06-27 15:53:05 -04:00
Claude 1069fb0701 refactor: move Profile Gallery Style setting to Profile UI settings
The gallery style selector is profile-specific, so it now lives on the
Profile UI settings screen alongside the other profile display toggles
instead of the general Application Preferences screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REGsru6cnm6wUzqm12Rh2d
2026-06-27 19:50:51 +00:00
Claude bdacc28663 feat: add app UI theme customization (accent color, font, font size)
Adds three new appearance settings to Application Preferences, alongside
the existing Theme selector:

- Accent Color: Purple (default), Blue, Green, Orange, Red, Pink. Drives
  the Material primary/secondary/tertiary colors so buttons, links, FABs
  and switches follow the chosen hue. Purple preserves the original look
  (purple primary + teal secondary).
- Font: System Default, Sans Serif, Serif, Monospace. Applied to the full
  Material typography and to bare Text via LocalTextStyle.
- Font Size: Small, Normal (default), Large, Huge. Scales all text through
  LocalDensity.fontScale without affecting dp-based layout.

Plumbed through the existing UiSettings -> UiSettingsFlow ->
UiSharedPreferences (DataStore) pipeline and the AmethystTheme composable.
New fields default to the current behavior and are appended, so existing
stored settings deserialize unchanged.

ColorScheme.isLight now derives from background luminance instead of a
fixed primary, so the light/dark check keeps working when a non-purple
accent is selected. The primary-derived tint extensions (links, new-item
background, secondary button) now compute from the live scheme so they
track the accent color.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01REGsru6cnm6wUzqm12Rh2d
2026-06-27 19:28:18 +00:00
Claude b01e1eb6bc feat(napplet): show top loading bar, keep splash until first paint, log load errors to console
The napplet/nsite host (NappletHostActivity) removed its loading splash the
moment the index probe succeeded and then mounted a WebView with no progress
tracking at all, so during the seconds the shell + bundle take to load (notably
over Tor) the user saw only the WebView's dark colorBackground — a black screen
with no sign anything was happening, especially in dark theme.

- Add a thin browser-style determinate progress bar pinned to the top edge,
  driven by WebChromeClient.onProgressChanged and hidden at 100%, to both the
  napplet/nsite host and the URL browser (NappletBrowserActivity).
- Mount the WebView under the loading splash and keep the splash (now opaque)
  until first paint (onPageCommitVisible) instead of removing it on mount, so
  there is never a blank/dark gap between probe-success and the shell's first
  frame. This mirrors the pattern the URL browser already used.
- Add a developer console (NappletConsolePanel) to the napplet/nsite host,
  wired through the existing onConsole hook in NappletControlSheet, and forward
  the page's console.* output to it.
- Surface failed resource fetches (onReceivedError / onReceivedHttpError) as
  ERROR lines in the console on both hosts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D4iYA4Qf5guWZyKexkcmhb
2026-06-27 19:20:54 +00:00
Claude fb76705f71 feat: discover followed nsites & napplets in the browser
Add two Discover sections to the browser launcher home that surface the
NIP-5A sites and NIP-5D apps published by the people the user follows,
reusing the same feed + follow-list filter as the dedicated nSites and
nApplets screens (set those to All Follows for a pure follows list).

The launcher subscribes the nsite/napplet assemblers while open, observes
the addressable manifest store, keeps the followed authors' (or own, in
the Mine case), drops ones already pinned, and caps each section. Rows
mirror the web Discover row — manifest icon, title, description, and a
star to pin — launching Nostr-natively through the sandboxed host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151Uczec41LhTogxkgoAhKa
2026-06-27 19:10:57 +00:00
Claude 217f4ad9ca fix: drop confirm dialog on nsite Tor network switch
Tapping the "loads over Tor" row on an nSite's pull-down sheet popped a
confirm dialog explaining the routing change. Users already know what Tor
is, so toggle the routing directly on tap (still relaunches the session to
rebuild the proxy + content server) and remove the now-unused strings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177rhf2L93YRcq6NrWkkM4Q
2026-06-27 19:06:37 +00:00