Commit Graph
15839 Commits
Author SHA1 Message Date
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
Vitor PamplonaandGitHub 6dd8771c2a Merge pull request #3407 from vitorpamplona/claude/cashu-icon-alignment-19eflw
Align Cashu icon with reaction gallery icons in Nutzap
2026-06-27 14:50:17 -04:00
Claude 152f81602c fix: align cashu nutzap icon with reaction icons in notifications
The nutzap gallery in MultiSetCompose used WidthAuthorPictureModifier
(55dp, flush-right) for its cashu icon column, while the like/boost
reaction galleries above it use NotificationIconModifier (55dp with a
5dp end inset). That left the cashu glyph sitting ~5dp further right
than the reactions it stacks under. Switch the cashu Box to the same
NotificationIconModifier so the icons line up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011qQdaD3DHRQNDKe2BUnEiM
2026-06-27 18:38:20 +00:00
Vitor PamplonaandGitHub 2e47eaf3a6 Merge pull request #3406 from vitorpamplona/claude/git-repositories-feed-7xov6e
Add Git Repositories feed screen with community and author filtering
2026-06-27 12:50:28 -04:00
Vitor PamplonaandGitHub 02aa877f4f Merge pull request #3405 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-27 12:45:07 -04:00
vitorpamplonaandgithub-actions[bot] fc95f64458 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-27 16:40:09 +00:00
Vitor PamplonaandGitHub be1b0bb851 Merge pull request #3404 from vitorpamplona/fix/loading-account-ui-thread-stall
fix(perf): unfreeze the cold-start "Loading account" screen in debug builds
2026-06-27 12:37:41 -04:00
Claude a81825b5cd feat: add Git Repositories feed
Add a new top-level feed for NIP-34 Git repository announcements
(kind 30617), mirroring the Pictures/Videos/Workouts feeds.

- GitRepositoriesFeedFilter scans LocalCache addressables for kind 30617
- Full top-nav filter support (follows, authors, global, hashtag,
  geohash, communities, muted) via a per-relay sub-assembler set
- Wired into AccountFeedContentStates, the relay subscription
  coordinator, bottom-bar preloaders, navigation, drawer and the
  persisted per-feed follow-list setting
- Reuses the shared RenderGitRepositoryEvent card via the standard feed
  render path, enriched with topic chips and a personal-fork badge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GS371vPHy3PhMfQyeAHmZC
2026-06-27 16:27:48 +00:00
Vitor PamplonaandClaude Opus 4.8 981fe528dc chore(debug): add Compose composition tracing deps (debug-only)
Adds androidx.compose.runtime:runtime-tracing and tracing-perfetto so
recompositions show up as named slices in Perfetto system traces — the
tool used to attribute the cold-start feed first-paint cost to specific
composables. debugImplementation only (not shipped); all Apache-2.0.
Usage (runtime-enable broadcast) documented inline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:26:45 -04:00
Vitor PamplonaandClaude Opus 4.8 de1362561f fix(perf): collect debug memory snapshot off the main thread
The debug-only MemoryUsageChip ("X/YMB" top-bar indicator, gated on
isDebug) polls collectMemorySnapshot() every 2s from a produceState
block, which runs on the main thread. That reads coil3.disk.DiskLruCache
.size(), a @Synchronized call. On cold start the Coil disk cache holds
that monitor for several seconds (journal init + the burst of image
writes from the initial relay event flood), so the UI thread blocked
inside size() — the "Loading account" frame couldn't repaint until it
returned. Profiling showed a single ~8s render frame and the UI thread
"blocking from coil3.disk.DiskLruCache.size()".

Collect the snapshot via withContext(Dispatchers.IO) so the synchronized
read blocks a background thread instead of the UI. The "Loading account"
stall on cold start drops from ~15-20s to ~5s. Debug-only path, so this
never affected release builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 12:26:44 -04:00
Vitor PamplonaandGitHub 9d0f444b6f Merge pull request #3403 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-27 08:56:59 -04:00
vitorpamplonaandgithub-actions[bot] 0afb4ada07 chore: sync Crowdin translations and seed translator npub placeholders 2026-06-27 12:55:24 +00:00
Vitor PamplonaandGitHub 7684c3725f Merge pull request #3400 from roguehashrate/feat/payment-targets
added support for more payment targets
2026-06-27 08:53:33 -04:00
Vitor PamplonaandGitHub 3832f31e52 Merge pull request #3401 from vitorpamplona/fix/video-decode-stall-fallback
fix(video): show browser-fallback overlay when a decoder silently stalls
2026-06-27 08:53:10 -04:00
Vitor PamplonaandGitHub ede3b798a3 Merge pull request #3402 from vitorpamplona/claude/web-app-preload-check-ev4r28
Cache favorited nsite/napplet manifests for offline resolution
2026-06-27 08:52:36 -04:00
Vitor PamplonaandClaude Opus 4.8 c0fadf9473 fix(video): show browser-fallback overlay when a decoder silently stalls
Some codec failures never surface as a PlaybackException: a software HEVC
decoder that can't keep up (e.g. iPhone-recorded hvc1 video on a device
without a HEVC hardware decoder) just parks the player in STATE_BUFFERING
forever — the buffer fills to the LoadControl cap, the playhead never leaves
0, and no error is ever raised. WatchPlaybackErrors only listened for
onPlayerErrorChanged, so the existing RenderPlaybackError "Open in browser"
overlay never showed and the user stared at a blank buffering box.

Add a decode-stall watchdog that polls the controller and synthesizes a
PlaybackException (ERROR_CODE_DECODING_FORMAT_UNSUPPORTED) once the player
sits in STATE_BUFFERING, wanting to play, with >=2s of media buffered ahead
(decoder is fed, not network-starved) yet a frozen playhead for 8s. The
buffer-ahead guard distinguishes a hung decoder from genuine network
starvation, whose buffer is depleted and so is never flagged.

Recovery is automatic: the overlay clears on the STATE_READY transition and,
belt-and-suspenders, the watchdog drops it the instant the playhead advances
again — so a slow device that eventually decodes "just plays." Also narrow
the recovery-clear to STATE_READY only (clearing on STATE_BUFFERING would
wipe the synthetic error instantly) and clear on onMediaItemTransition so a
pooled player starting a new video resets cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 08:49:29 -04:00
roguehashrate cac768175c added support for more payment targets 2026-06-26 21:35:06 -05:00
Claude 7340d93f0c feat: preload and cache pinned nsite/napplet manifests
Pinned web apps in the bottom nav warm reliably because EmbeddedTabFactory
only needs their URL, but a pinned nsite/napplet (FavoriteApp.NostrApp) could
not warm: favorites store only a kind:pubkey:dtag coordinate, and nothing
pulled that addressable manifest into LocalCache until the user opened the
napplet/nsite discovery screen. So embedParams() returned null and the
EmbeddedTabPreloader gave up.

Add FavoriteAppManifestPreloader, mounted once in the logged-in shell
(independent of the API-30 embedded-surface gate, since the full-screen
launcher benefits too). For each NostrApp favorite it drives the existing
EventFinder (via observeNote) to fetch the manifest's coordinate into
LocalCache, so the preloader and launcher can resolve it.

Also cache the resolved manifest event JSON device-locally in
FavoriteAppsRegistry (a second DataStore key, same single-key shape as the
favorites list) and seed LocalCache from it when relays stay silent shortly
after launch, so a pinned nsite/napplet resolves instantly and offline on the
next cold start. The cached copy is re-verified (wasVerified=false) before it
enters the cache, and refreshed whenever a newer manifest arrives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LroBCry1UiXWf9Y4fk4b9h
2026-06-27 01:43:38 +00:00
Vitor PamplonaandGitHub d8ab76d536 Merge pull request #3399 from vitorpamplona/claude/android-accountviewmodel-store-xy21sw
refactor: scope account ViewModelStore with Lifecycle 2.11 rememberViewModelStoreOwner
2026-06-26 21:02:57 -04:00
Claude b3a4e13b85 refactor: scope account ViewModelStore with Lifecycle 2.11 rememberViewModelStoreOwner
The per-account ViewModelStore was managed by a hand-rolled registry
(StoreOwnerRegistry + ScopedViewModelStoreOwner + a RememberObserver) that
tracked configuration changes manually. Its own TODO admitted it could not
clear a store detached around a configuration change, so AccountViewModels
(and their child ViewModels, feed states and relay subscriptions) leaked and
stayed active after switching accounts.

Replace the whole registry with androidx.lifecycle 2.11's
rememberViewModelStoreOwner (already on the classpath at 2.11.0). The owner is
keyed by the account public key via key(): while an account stays logged in
the owner survives recompositions and configuration changes (it is parented to
the Activity's LocalViewModelStoreOwner); when the account changes the previous
owner leaves the composition and its ViewModelStore is cleared immediately.

Deletes ~95 lines of lifecycle plumbing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0154te1AiD1Ykz1HCa8ao2Vo
2026-06-27 00:50:32 +00:00
Vitor PamplonaandGitHub 23a1c8af13 Merge pull request #3398 from vitorpamplona/claude/fix-remember-feed-content-padding-7ipbs4
Move rememberFeedContentPadding to commons.ui.layouts
2026-06-26 20:41:33 -04:00
Claude a101f53935 fix: correct rememberFeedContentPadding import path in GitItemListRow
The import referenced the non-existent
com.vitorpamplona.amethyst.ui.layouts package. The function lives in
commons under com.vitorpamplona.amethyst.commons.ui.layouts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q1LfVV9ZRuktCa7XQTzsLe
2026-06-27 00:40:18 +00:00
Vitor PamplonaandGitHub 8607a44ebd Merge pull request #3390 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-06-26 20:37:33 -04:00
Vitor PamplonaandGitHub 1fc98e0687 Merge pull request #3396 from vitorpamplona/claude/reproducible-build-dsigxh
Make Arti native library builds reproducible
2026-06-26 20:37:15 -04:00
Claude 6bb2f8045d build: pre-merge audit fixes for reproducibility work
Bugs / inconsistencies found while reviewing the branch for merge:

- dependenciesInfo comment falsely claimed Play "still derives this data
  server-side, nothing is lost." Not true: includeInBundle=false means the
  .aab carries no dependency metadata, so Play Console's dependency-insights /
  SDK-vulnerability alerts go unpopulated (uploads still succeed). Corrected
  the comment and the BUILDING.md framing (it called the blob "the one
  remaining blocker" when the Arti .so was the bigger one).

- Version-bump workflow was broken: the README told you to run
  `build-arti.sh --clean` to refresh Cargo.lock, but the build is now --locked
  (fails on a stale lock) and the clone moved to the canonical /tmp path. Added
  a dedicated `--regen-lock` mode (clone + cargo generate-lockfile, no NDK
  needed) and pointed the docs at it. Verified it reproduces the committed lock
  byte-for-byte.

- verify-reproducible.sh: new helper that builds twice and diffs to prove
  byte-for-byte reproducibility; uses portable sha256 (sha256sum/shasum) and
  plain `sort` so it runs on macOS too.

- README verify recipe referenced paths that only resolved from the repo root
  while telling you to cd into tools/arti-build — replaced with the helper.

- rust-toolchain.toml listed four Android targets but only two ABIs ship a
  .so; trimmed to match (check_prerequisites adds any other on the fly).

- BUILDING.md: documented that the bundled Arti .so is reproducible-from-source
  and that secp256k1/webrtc are version-pinned Maven prebuilts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JtjUcSjjpu4auFndw1QKeU
2026-06-27 00:19:26 +00:00
vitorpamplonaandgithub-actions[bot] cc72e8045a chore: sync Crowdin translations and seed translator npub placeholders 2026-06-27 00:14:28 +00:00
Vitor PamplonaandGitHub ceca5d52ae Merge pull request #3394 from vitorpamplona/claude/composable-memory-ci-build-jxpwkv
Move UI components to commons module for better code sharing
2026-06-26 20:12:09 -04:00
Claude 94677d1677 refactor: extract self-contained layout/preview leaves to :commons
Continues moving genuinely platform-agnostic leaves out of the :amethyst app
module so they compile once in :commons instead of across all six app variants.

Moved (no Android coupling, no foundation deps):
  - ui/layouts/DisappearingBarState, DisappearingBarNestedScroll, PaddingMerge
    -> commons commonMain (com.vitorpamplona.amethyst.commons.ui.layouts)
  - ui/components/UrlPreviewState
    -> commons jvmAndroid (it references commons.preview.UrlInfoItem, which
       lives in the jvmAndroid source set)

Consumers (incl. the existing DisappearingBar*Test unit tests, which stay in
:amethyst and now import from commons) updated to the new packages. No behavior
change.

Verified: :commons, :amethyst compilePlayDebugKotlin + compilePlayDebugUnitTest,
and :desktopApp:compileKotlin build clean; spotless applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNKcfjNszUZPQShYJjfmnf
2026-06-27 00:06:52 +00:00
Vitor PamplonaandGitHub 2dc80f899e Merge pull request #3393 from vitorpamplona/claude/repo-tabs-status-split-p2ducw
Add Open/Closed filter to Git repo Issues and Patches tabs
2026-06-26 19:40:59 -04:00
Claude ed3a893d9d build: build Arti at a canonical path for cross-environment reproducibility
Empirical finding: with the toolchain pin, locked deps, and
--remap-path-prefix all in place, two host builds of libarti_android.so at
the *same* path are byte-for-byte identical, but two builds at *different*
paths still differ — not in any embedded string (no path leaks into the
binary) but in the order rustc lays out functions/data, which it derives
from the real on-disk artifact paths. --remap-path-prefix only rewrites
embedded strings, not that internal ordering.

So compile in a fixed location (/tmp/amethyst-arti-build, overridable via
ARTI_REPRO_DIR) in both build-arti.sh and build-arti-host.sh. Any checkout
then produces matching bytes, which is what lets F-Droid / a verifier build
at the same canonical path and reproduce the shipped .so. This mirrors how
Rust libraries are reproduced elsewhere (F-Droid builds Rust at a fixed
path too).

Corrects the README, which previously implied path remapping alone gave
path-independent output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JtjUcSjjpu4auFndw1QKeU
2026-06-26 23:40:18 +00:00
Vitor PamplonaandClaude Opus 4.8 13892db3ae Merge PR: fix(account): adding a read-only npub no longer clobbers a signing account
Merges nostr proposal e283c388 into main (replaces the closed e05208d9 with a
minimal guard). Adding/scanning your own read-only npub for a pubkey you already
hold the nsec for no longer downgrades the signing account — on Android it
wiped cached lists + disabled notifications, on desktop it orphaned the key.
Guarded at the single persistence point on each platform; desktop regression
test verified failing without the guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:24:01 -04:00
Claude f7a15a8407 build: make the Arti (Tor) native build reproducible
The libarti_android.so shipped in the APK is the one binary we compile
ourselves, and it was the remaining blocker to a verifiable build: a Rust
cdylib is only reproducible when the compiler, the dependency graph, and the
embedded build paths are all pinned. None were.

Pin all three:
- rust-toolchain.toml pins rustc (rustup auto-installs it + the Android
  targets), so codegen is stable across machines.
- Cargo.lock is now generated and committed (501 packages); both build
  scripts run `cargo --locked` so transitive versions can't drift.
- repro-env.sh (sourced by build-arti.sh and build-arti-host.sh) rewrites
  host-specific absolute paths with --remap-path-prefix, disables incremental
  compilation, and sets a fixed SOURCE_DATE_EPOCH derived from the Arti tag.

With these, an independent rebuild of the pinned tag reproduces the committed
.so bit-for-bit, which is what lets F-Droid / Zapstore verify it from source
instead of trusting a prebuilt blob. README documents the pins and a
two-path build-and-diff verification recipe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JtjUcSjjpu4auFndw1QKeU
2026-06-26 23:01:48 +00:00
Vitor PamplonaandClaude Opus 4.8 0d0aba90c9 fix(account): don't let adding a read-only npub clobber an existing signing account
Accounts dedup by npub (= pubkey), so adding/scanning your own read-only npub for
a pubkey you already hold the nsec for would overwrite the signing account:
- Android: setDefaultAccount rewrote the per-npub file from fresh read-only
  settings — wiping the account's cached follow/relay/mute lists and flipping
  hasPrivKey off, which silently disables its push notifications (every
  notification path early-returns on !hasPrivKey). The account looked lost ("can't
  post anymore") even though the nsec survived on disk.
- Desktop: saveCurrentAccount overwrote signerType to ViewOnly, orphaning the
  stored key and routing every later switch through loadReadOnlyAccount.

Guard the downgrade at the single persistence point on each platform: when the
account being made current is read-only and a SIGNING account already exists for
the same pubkey, keep the signing account and switch to it instead. A signing
account already subsumes a read-only one, so this loses nothing.

- Android LocalPreferences.setDefaultAccount now returns the settings that
  actually became current; AccountSessionManager.loginAndStartUI shows that.
- Desktop AccountManager.saveCurrentAccount reuses switchAccount() to reload the
  signing account. Covered by a regression test (verified failing without the
  guard).

This replaces the closed proposal e05208d9, which solved the same underlying bug
with a much heavier accountId rework (separate npub/nsec switcher entries — a
niche feature) that itself shipped a logout(deleteKey=true) data-loss bug.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 19:00:51 -04:00
Claude a8db6674c4 feat: compact Issue/PR list rows on the git repository screen
Render the repository screen's Issues and Patches & PRs tabs with a
lightweight one-line-per-item row (author picture, name, NIP-05, subject,
time, status pill and the shared 3-dot options) instead of the full
NoteCompose renderer, which is tuned for items shown inside a regular
feed. Injected via RefresheableFeedView's existing onLoaded slot, so the
feed filters and status-split view models are untouched.

The rows reuse and re-layer the same gating NoteCompose applies — event
loading (WatchNoteEvent), mute/block/report hiding
(CheckHiddenFeedWatchBlockAndReport) and the long-press quick-action menu
— so blocked authors and reported items are hidden here exactly as
elsewhere. No body or media is rendered, so sensitive content never
reaches this list.

Adds GitPatchEvent.subject(), which parses the patch title from the
git-format-patch Subject header (stripping the [PATCH n/m] prefix and
unfolding continuation lines), with unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gs6pxiq58X18Fkz9wdZhU
2026-06-26 22:55:46 +00:00
Vitor PamplonaandClaude Opus 4.8 2ee0d91b71 Merge PR: feat(cli): amy namecoin resolve + servers verbs
Merges nostr proposal 488e8447 (v2) into main: adds `amy namecoin resolve`
+ `amy namecoin servers` (stateless ElectrumX Namecoin resolution over the
quartz NamecoinNameResolver). The v2 revision fixes the `--server` override to
reuse the shared NamecoinSettings.parseServerString so it keeps
usePinnedTrustStore=true (self-signed Namecoin servers otherwise fail TLS),
plus strict --timeout parsing and accurate docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:31:42 -04:00
Claude 8b74a3e24e refactor: extract 7 pure leaf composables from :amethyst to :commons
First Tier A slice of the UI/components extraction. Moves the genuinely
platform-agnostic leaf composables — those with zero :amethyst
dependencies and no Android coupling — from the app module into the
shared :commons KMP module (commonMain):

  ClickableTexts, ForwardingPainter, GenericLoadable, GlowingCard,
  LoadingAnimation, TranslationConfig, ZonedSwipeModifier  (~616 LOC)

These now live under com.vitorpamplona.amethyst.commons.ui.components and
compile once in :commons (cacheable, incremental) instead of being part
of every one of the six :amethyst variant compilations (play/fdroid ×
debug/release/benchmark). That shrinks the app-module Kotlin compilation
unit — the root cause of the CI Kotlin-daemon OOM — rather than renting
headroom with heap flags.

Consumers updated to import from the new package; no behavior change.
Verified: :commons, :amethyst compile{Play,Fdroid}DebugKotlin, and
:desktopApp:compileKotlin all build clean; spotless applied.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNKcfjNszUZPQShYJjfmnf
2026-06-26 22:29:37 +00:00
Vitor PamplonaandClaude Opus 4.8 112ef0536a fix(cli): --server reuses NamecoinSettings.parseServerString (keeps pinned trust store)
`amy namecoin resolve --server` hand-rolled its own ElectrumX server-string
parser that constructed `ElectrumxServer(host, port, useSsl)` and left
`usePinnedTrustStore` at its `false` default. The Namecoin ElectrumX servers
use self-signed certs, so a TLS connection with the default system trust
manager fails the handshake — meaning `--server electrumx.testls.space:50002`
could not connect even though that exact host resolves fine via the default
list. It also duplicated logic already in `commons`, violating the cli
thin-assembly-layer rule.

Delegate each comma-separated entry to the shared
`NamecoinSettings.parseServerString` (the same parser the Android/Desktop
Settings use), so the CLI inherits both the `host:port[:tcp]` syntax and
`usePinnedTrustStore = true`. The README claim that it "reuses the same …
pinned trust store as the apps" is now actually true for `--server` overrides.

Also: reject a non-integer `--timeout` as bad_args instead of silently
falling back to the default, and document exit code 2 + the `host:port[:tcp]`
syntax accurately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:27:32 -04:00
mstrofnoneandVitor Pamplona 305d6bc733 feat(cli): amy namecoin resolve + servers verbs
Add Namecoin NIP-05 resolution to the amy CLI as a stateless verb
group, matching the Android and Desktop apps' resolution surface.

  amy namecoin resolve IDENT [--server URL[,URL]] [--timeout SECS]
  amy namecoin servers

IDENT accepts the same shapes the apps accept: raw `d/` / `id/`
names, bare `.bit` domains, and `alice@example.bit` NIP-05-style
local-parts. Output is the resolved Nostr pubkey + relay list (+ the
resolved Namecoin name + matched local-part) as machine-readable
JSON (with `--json`) or human-readable text.

The verb is stateless — no account, no `~/.amy/`, no relays — so it
dispatches alongside `decode`/`encode`/`verify`/`nip`/`kind` before
account resolution and the secret store.

Zero new logic in cli/: the implementation is a thin command-file
wrapper around quartz's existing `NamecoinNameResolver` +
`ElectrumXClient` + the canonical `DEFAULT_ELECTRUMX_SERVERS` set
the apps already ship with, including the pinned trust store for
the self-signed Namecoin ElectrumX ecosystem.

amy is headless so no UI piece is wired in. The `--server` flag
accepts `host`, `host:port`, `tcp://`, `tls://`, `ssl://` per entry
(defaults to TLS on 50002); empty / malformed entries fail with
`bad_args` rather than silently using the default set, so a fat-
fingered override can't go unnoticed.

Outcomes from `NamecoinResolveOutcome` map to amy error codes:
  Success           -> emit JSON, exit 0
  NameNotFound      -> error not_found
  NoNostrField      -> error no_nostr_field
  MalformedRecord   -> error malformed_record (+ namecoin_name extra)
  ServersUnreachable-> error servers_unreachable
  InvalidIdentifier -> error invalid_identifier
  Timeout           -> error timeout

Smoke-tested end-to-end on macOS arm64 against the live ElectrumX
fleet:

  $ amy --json namecoin resolve d/testls
  {"identifier":"d/testls","namecoin_name":"d/testls",
   "local_part":"_","pubkey":"460c25e6…","relays":[]}

  $ amy namecoin servers
  count:   6
  servers:
    - host: electrumx.testls.space
      port: 50002
      tls:  yes
    …

No new runtime deps. The "no Compose UI in the amy image" CI
assertion still passes — `NamecoinNameResolver` + `ElectrumXClient`
are pure JVM (kotlinx.coroutines + kotlinx.serialization, both
already on the CLI classpath via :quartz).

Tests: the resolver, ElectrumX client, identifier parser, and the
default server set already have JVM tests under
`quartz/src/jvmTest/.../namecoin/` — no new core code in this PR,
so the existing coverage applies. CLI verbs are exercised via the
shell harnesses in `cli/tests/`; a Namecoin harness fits the same
pattern but isn't included here.

Parity matrix in `cli/ROADMAP.md` flags `name_history` and the
Namecoin Core JSON-RPC backend as pending separate PRs — both
already exist on Android and Desktop but aren't on upstream main
yet (open PRs against this repo carry them).
2026-06-26 18:23:13 -04:00
Claude b9fa242e68 feat: split git repo Issues and Patches & PRs tabs by status
Within the repository route's Issues and Patches & PRs tabs, add an
Open / Closed & Resolved segmented selector so items are partitioned by
their latest NIP-34 status. Open covers no-status, open (1630) and draft
(1633); Closed & Resolved covers closed (1632) and applied/merged (1631).

The feed filters now take a showClosed flag and consult GitStatusIndex.
Because a status event (kinds 1630-1633) doesn't mutate the issue/patch
note, the additive feed update can't move an item between buckets on its
own, so each view model watches GitStatusIndex.latestByTarget and forces
a full re-partition whenever it changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013gs6pxiq58X18Fkz9wdZhU
2026-06-26 22:22:08 +00:00
Vitor PamplonaandClaude Opus 4.8 f5793937a7 Merge PR: fix(desktop): resolve Namecoin names in the home tab search bar
Merges nostr proposal ae364d99 into main: adds Namecoin (.bit) name
resolution to the desktop home-tab search bar (desktopApp FeedScreen.kt).
Network IO runs off the UI thread, stale lookups cancel via effect re-keying,
and it reuses the shared NamecoinNameResolver. (Nit deferred: extract a shared
rememberNamecoinResolution helper to de-dup with SearchScreen.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:19:23 -04:00
Vitor PamplonaandGitHub c67413544e Merge pull request #3392 from vitorpamplona/claude/ship-accrescent-6t68th
Add Accrescent APK set build & remove cleartext traffic flag
2026-06-26 18:02:53 -04:00
Vitor PamplonaandGitHub a56b5eff20 Merge pull request #3391 from vitorpamplona/docs/agent-ngit-pr-workflow
docs(claude): document the ngit + GitHub PR workflow for agents
2026-06-26 18:02:36 -04:00
Vitor PamplonaandClaude Opus 4.8 36ae65765d docs(claude): document the ngit + GitHub PR workflow for agents
Adds a `.claude/skills/ngit-pr` skill and a pointer from CLAUDE.md so
agents know how to create, review, revise, and merge PRs in this repo.

This repo can publish a PR two ways and the difference is easy to get
wrong: a GitHub remote (canonical `main`, normal `gh` flow) and a
git-over-nostr remote (`ngit`, where PRs are nostr proposals on
gitworkshop.dev and a push fans out to GitHub + the GRASP servers).

The skill identifies remotes by URL (names vary per clone; a collaborator
may have only one), explains which path to use, and documents the
three-mains alignment gate (GitHub vs the lagging nostr `main` vs local
`main`) that the nostr create/revise/merge flows all depend on — the
thing that otherwise causes rejected pushes and revisions that never
appear on gitworkshop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:55:34 -04:00
Vitor PamplonaandClaude Opus 4.8 f219153a7d Merge PR: fix(tor): wire Onion-Location interceptors into every OkHttp client
Merges nostr proposal e5865428 (v2) into main:
- fix(tor): wire Onion-Location interceptors into every OkHttp client
  (onionCache made non-nullable; OnionInterceptorWiringTest)
- refactor(napplet): route blob fetches through the shared OkHttpClientFactory
- fix(napplet): route brokered resource fetches by the applet's own Tor mode

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:41:28 -04:00
Vitor PamplonaandClaude Opus 4.8 1dbfd78b44 fix(napplet): route brokered resource fetches by the applet's own Tor mode
The consolidation passed `useProxy = true` for every brokered `resource.bytes`
fetch, forcing them through Tor whenever Tor was active — regardless of the
napplet/nSite's actual network mode. That overrides the user's explicit choice:
an nSite running in "open web" mode would still have its blob fetches tunneled,
inconsistent with how its own WebView page loads.

The authoritative per-applet preference already exists main-side in
NappletNetworkRegistry.useTor(coordinate) (locked napplets pinned to Tor;
nSites follow the persisted per-site toggle, which relaunches on change) — the
same source NappletLauncher reads to set the WebView proxy. Thread the calling
applet's coordinate through NappletResourceGateway.fetch so the broker can
resolve it, and pick the shared client with
getHttpClient(useProxy = NappletNetworkRegistry.useTor(coordinate)). This
mirrors the host's own `effectiveProxy = if (useTor) proxyPort else -1` exactly,
so a brokered fetch now routes like the applet's page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:32:57 -04:00
Vitor PamplonaandClaude Opus 4.8 c797033ba5 refactor(napplet): route blob fetches through the shared OkHttpClientFactory
The t4 onion-location proposal hand-wired OnionLocationInterceptor +
OnionUrlRewriteInterceptor into NappletResourceFetcher's private,
torPort-keyed OkHttpClient. That reached onion-routing parity but
duplicated the exact wiring OkHttpClientFactory already does, and the
private client still missed the local Blossom cache redirect, the shared
connection pool / HTTP-2 keepalive, and SurgeDns.

Inject the app-wide client instead: NappletResourceFetcher now takes a
() -> OkHttpClient and the broker supplies
`okHttpClients.getHttpClient(useProxy = true)` — the same DualHttpClientManager
path the image pipeline uses. Behavior-preserving for Tor (proxied when
Tor is active, clearnet when not) and, since these are sha256 blobs, the
shared Blossom-cache redirect is now a feature, not a loss. Drops the
private client + its cache and the hand-wired interceptors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 17:32:57 -04:00
mstrofnoneandVitor Pamplona 6209378b83 fix(tor): wire Onion-Location interceptors into every OkHttp client
Pins the Onion-Location interceptor wiring introduced in #3368 across
every OkHttp client the app builds and closes two gaps the audit
surfaced:

* OkHttpClientFactory.onionCache was nullable with a null default.
  A future call site constructing the factory without explicitly
  passing the cache would silently disable onion-routing for the
  entire HTTP role (image, upload, money, NIP-05, preview, push).
  Tightened to required (matches DualHttpClientManagerForRelays).

* NappletResourceFetcher built a raw OkHttpClient with no interceptors.
  Napplet HTTPS / blossom fetches over Tor would hit clearnet exit
  nodes even when the destination advertised an onion. Wired through
  the app-wide OnionLocationCache so a hint learned anywhere in the
  app applies, and vice versa.

ElectrumX is intentionally excluded: it uses raw Socket/SSLSocket,
not OkHttp, so the Onion-Location HTTP header does not apply. Tor
routing for ElectrumX continues to go through the Tor-aware
SocketFactory plus the Namecoin _tor field on the record (a
stronger, blockchain-anchored trust path than a passive HTTP hint).

IsEmulator is made null-safe (each Build.* field coalesced to "") so
unit tests can stand up the affected classes without NPEing on the
JVM default-values stub of android.os.Build.

New OnionInterceptorWiringTest (11 cases) covers:
  * locationInterceptor records the header under the clearnet host
    (HTTPS path and WebSocket 101 upgrade)
  * locationInterceptor with no header writes nothing
  * rewriteInterceptor passes through unknown hosts
  * rewriteInterceptor https -> https.onion preserves scheme
  * rewriteInterceptor https -> http.onion downgrades safely
  * rewriteInterceptor passes through unparseable cache values
  * cache round-trip and shared-instance invariant
  * compile-time pin that both classes remain okhttp3.Interceptor

Build:
  ./gradlew --no-daemon spotlessCheck                                       OK
  TZ=UTC ./gradlew --no-daemon :amethyst:testPlayDebugUnitTest              763 tests, 0 failures
  ./gradlew --no-daemon :commons:verifyKmpPurity :quartz:verifyKmpPurity    OK
2026-06-26 17:32:57 -04:00
Claude 284b7b143e ci: build signed Accrescent APK set (.apks) on release
Accrescent only accepts a signed APK set of split APKs generated by
bundletool from an AAB — not the AAB itself and not a monolithic APK.
After signing the F-Droid AAB, run bundletool build-apks (--mode=default)
to emit dist/amethyst-fdroid-<tag>.apks, signed with the same release
keystore secret the other signing steps use. It is attached to the GitHub
Release via the existing dist/* glob.

Upload to Accrescent stays manual (drag the .apks into the developer
console): Accrescent has no publish API or CI CLI yet — both are on their
roadmap but unreleased. A build-time guard warns if the APK set exceeds
Accrescent's 128 MiB automated-check limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rE4k6sUN39emsofSv1Y1M
2026-06-26 21:27:16 +00:00
Vitor PamplonaandGitHub 2e69f85255 Merge pull request #3389 from vitorpamplona/claude/pr-rendering-layout-uafuio
Style Git pull request clone URLs with labelMedium typography
2026-06-26 17:19:45 -04:00