Commit Graph
303 Commits
Author SHA1 Message Date
Claude 90150e72e4 fix: upgrade privacysandbox.ui to alpha17 (embedded drag/scroll input)
The embedded surface forwarding taps but cancelling drags was a known bug in
androidx.privacysandbox.ui alpha10: with the provider surface z-ordered above,
"the gesture is exclusively received by the provider window and not transferred
to the client window" (alpha13 release notes). alpha15 then "set the default
Z-ordering to below" and "added support for the UI provider to receive
MotionEvents in this mode after being received by the client window" — i.e. the
drag-input path we needed.

Bump alpha10 → alpha17 and adapt the changed API: openSession takes SessionData
instead of a windowInputToken IBinder, Session adds notifySessionRendered, and
the session-state listener became setEventListener(SandboxedSdkViewEventListener)
(ready now flips on onUiDisplayed). The direct-WebView browser is unaffected
(it doesn't use this library).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMpRcWj6y82LxLiwcuzmN
2026-06-23 23:16:02 +00:00
Claude d63bf87d2a feat: add in-app browser tab rendered from the keyless napplet sandbox
Adds a "Browser" navigation destination (drawer + pinnable bottom-nav item,
API 30+) that opens any URL. The page renders in the sandboxed, keyless
`:napplet` process and is streamed into the main activity as a cross-process
surface via androidx.privacysandbox.ui (SurfaceControlViewHost) — only pixels
and input cross the boundary, never the WebView's JS context or the NIP-07
bridge. The trusted address bar is drawn by the main process around the
embedded surface, so the sandbox can never spoof the URL.

NIP-07 `window.nostr` is injected the same way nSite website mode does it, but
scoped per visited origin: each origin gets its own broker-minted launch token
(keyed by the trusted source origin), so a grant to one site never leaks to
another.

- NappletBrowserService (`:napplet`): hosts the live-URL WebView, exposes it as
  a SandboxedUiAdapter, and relays the per-origin NIP-07 bridge to the broker.
- NappletBrowserUiAdapter: wraps the WebView session for privacysandbox.ui.
- NappletBrokerService: mints a per-origin synthetic identity so NIP-07 consent
  is scoped per host.
- EmbeddedBrowserController + BrowserScreen: bind the service, render the
  SandboxedSdkView, and drive the trusted address bar (navigate/reload/back/Tor).
- shim.js: a direct-bridge transport so the injected shim works in a top-level
  page that has no trusted shell parent.
- Browser nav item hidden below API 30 (SurfaceControlViewHost requirement).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgMpRcWj6y82LxLiwcuzmN
2026-06-23 03:39:06 +00:00
Claude 4218a7c1af refactor(napplets): extract sandbox runtime into :nappletHost module
The `:napplet` sandbox runtime (NappletHostActivity, NappletContentServer,
NappletIpc, NappletKeyActions) now lives in a new :nappletHost Android library
that depends only on :commons + :quartz — NEVER :amethyst. So the sandbox code
is compile-time incapable of importing Amethyst.instance / LocalCache / Account,
turning the "two-process, no secrets in the sandbox" rule from a convention into
a build-graph guarantee.

- New module + NappletHostContract (Intent-extra keys + broker service FQN), so
  the launcher (amethyst) and activity (module) share the launch contract with no
  dependency cycle. The activity binds the broker by class name.
- Capability labels for the "what it can access" sheet are resolved by the
  launcher (which has app resources) and passed in, so the module needs no
  capability string resources. Host-only strings moved into the module.
- amethyst depends on :nappletHost; the broker-side (NappletBrokerService,
  gateways, NappletLaunchRegistry) stays in :amethyst. Manifest declares the
  activity by FQN (keeps @style/Theme.Amethyst resolvable).
- Docs updated (CLAUDE.md + security plan).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-22 20:45:19 +00:00
Claude df15414424 Merge remote-tracking branch 'origin/main' into claude/awesome-pasteur-xwiwad
# Conflicts:
#	amethyst/src/main/res/values/strings.xml
2026-06-22 14:50:16 +00:00
Claude daae12b53d feat(napplet): Android sandbox host — isolated process, broker IPC, consent
Implements the Android side of the napplet/nsite trust boundary on top of the
commons core. The applet runs in a separate OS process holding no keys; every
dangerous operation is brokered to the main process and gated by user consent.

:napplet process (no secrets):
- NappletHostActivity: hardened WebView (no file/content access, no DOM storage,
  mixed-content blocked, SafeBrowsing on), applet served into an opaque-origin
  sandboxed iframe, manifest blobs served already-verified via
  shouldInterceptRequest with a default-deny CSP (connect-src 'none' = no direct
  network), and a window.napplet.* shim bridged over an origin-restricted
  WebMessageListener.

Main process (holds the signer):
- NappletBrokerService: bound Messenger service running the commons NappletBroker
  against the live account; exported=false + UID check. Builds the broker per
  request so account switches are honored; relay publish via the account's
  computed broadcast relays.
- NappletConsentActivity + NappletConsentCoordinator: capability-consent dialog
  with a suspend bridge; fails closed on dismissal.
- DataStoreNappletPermissionStore: persistent grant store.

Shared edge: NappletProtocolJson (JSON codec), NappletIpc (Messenger contract),
NappletLauncher (packs a verified manifest into the host Intent), shell.html.

Adds androidx.webkit (Apache-2.0) for the origin-restricted message bridge — a
plain @JavascriptInterface leaks into every frame and would break the boundary.
Manifest declares the :napplet activity, the consent activity, and the broker
service. :amethyst:compileFdroidDebugKotlin passes; on-device verification and a
UI entry point are the remaining steps (see the plan doc).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ncMHuBBVHEf7spAoSssde
2026-06-19 23:50:58 +00:00
Vitor PamplonaandClaude Opus 4.8 805f7e8e84 v.1.12.6
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:01:59 -04:00
Vitor PamplonaandClaude Opus 4.8 3d26a3d818 v.1.12.5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:55:37 -04:00
Vitor PamplonaandClaude Opus 4.8 c006831f9c v.1.12.4
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:32:52 -04:00
Vitor Pamplona 7096819f3b v.1.12.3 2026-06-19 11:13:00 -04:00
Vitor Pamplona b8312f023a v.1.12.2 2026-06-19 09:22:33 -04:00
Claude a9ce0df8f2 feat: render road event (kind 1315/1316) location on an OSM map
Replace the reverse-geocoded "📍 City" line on the Roadstr road event
report and confirmation cards with an osmdroid OpenStreetMap preview
pinned at the event's coordinates.

- Add osmdroid-android 6.1.20 (Apache-2.0) to the version catalog and
  the amethyst module.
- New LocationPreviewMap composable: an AndroidView-wrapped MapView with
  MAPNIK tiles, a single marker, lifecycle-aware onResume/onPause/onDetach,
  and nested-scroll-friendly touch handling so panning the map doesn't
  fight the feed scroll. Sets the OSM User-Agent to the package name
  (required or OSM returns 403).
- RoadEvent cards resolve a point from the explicit lat/lon tags, falling
  back to the center of the finest published geohash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017tYbcy4UGWxqQbcycyL7Yd
2026-06-18 21:36:56 +00:00
Claude 644ccaedd5 chore(deps): bump stable dependency versions
Update to latest stable releases:
- Compose BOM 2026.05.01 -> 2026.06.00 (Compose core patch 1.11.2 -> 1.11.3)
- AndroidX Lifecycle 2.10.0 -> 2.11.0
- Firebase BOM 34.14.1 -> 34.15.0 (firebase-messaging 25.0.2 -> 25.1.0)
- google-services plugin 4.4.4 -> 4.5.0
- kotlin-test 2.3.21 -> 2.4.0 (align with kotlin 2.4.0)
- KSP 2.3.8 -> 2.3.9
- Spotless 8.6.0 -> 8.7.0

All stable, no breaking changes affecting Amethyst: no Navigation3 usage
(Lifecycle 2.11 nav decorator break N/A), Spotless pins ktlint 1.7.1 (no
reformat churn), and Firebase FCM deprecations are warnings only (no
allWarningsAsErrors). Verified: spotlessCheck, quartz compile + jvmTest pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PDjG6Lgp4SmGJ9vuD7XwDD
2026-06-17 23:19:27 +00:00
Vitor PamplonaandClaude Opus 4.8 02ee493502 chore(release): 1.12.1
Bump the app version to 1.12.1 (versionCode 449) in the catalog, which now
drives every module. Add the v1.12.01 changelog (Health Connect workouts,
share-as-image, immersive system bars, deterministic Tor Active fix) and index
it. Refresh the quartz-integration skill's artifact-version references.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:20:18 -04:00
Vitor PamplonaandClaude Opus 4.8 270d229d0a build: drive all module versions from the catalog
Make every module read its release version from gradle/libs.versions.toml
instead of carrying its own literal, so a release bump is a single-file edit.

- Move the Android versionCode out of amethyst/build.gradle.kts into the
  catalog as `appCode`; amethyst reads it via libs.versions.appCode.get().
- quartz publishes with version = libs.versions.app.get().
- geode generates a BuildConfig.VERSION from the catalog (new
  generateVersionFile task) and RelayInfo.VERSION reads it, so the NIP-11
  software version tracks releases.
- Update the root build comment to point at the appCode entry.

Version-neutral: everything still resolves to the current 1.12.0 / 448.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:19:52 -04:00
Claude 6de43dec2d feat: detect workouts from Health Connect and suggest a kind 1301 post
Adds foreground auto-detection of finished workouts on Android via Google
Health Connect — the single aggregator every Android health source funnels
into (Samsung Health/Galaxy Watch, Google Fit, Fitbit, Garmin, Strava), the
same Android path RUNSTR uses. On opening the Workouts screen, Amethyst scans
Health Connect for sessions the user hasn't handled and surfaces a banner that
opens the existing workout composer pre-filled, ready to publish as a NIP-101e
WorkoutRecordEvent (kind 1301).

- HealthConnectManager reads ExerciseSessionRecord + aggregated distance,
  calories, heart rate, steps and elevation, mapping each to DetectedWorkout.
- ExerciseTypeMapper maps Health Connect activity types to NIP-101e verbs.
- HealthConnectStore remembers handled sessions per account so each is
  offered once; 7-day foreground lookback, no background service.
- WorkoutSuggestions banner: connect prompt (on-demand permission request,
  never on cold start) or detected-workout rows on the Workouts screen.
- Route.NewWorkout carries optional pre-fill; NewWorkoutViewModel publishes the
  richer metrics (heart rate, steps, elevation, start time) with
  source=health_connect (new SourceTag constant in quartz).
- androidx.health.connect:connect-client (Apache-2.0) + read-only health
  permissions and the required privacy-rationale manifest entries.

Design notes in amethyst/plans/2026-06-16-health-connect-workout-detection.md;
background ~15-min polling + notification left as a documented future seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qgqQKHSewRHVM8vSCLt9P
2026-06-16 22:19:58 +00:00
Vitor Pamplona 0572b0091c v1.12.0 2026-06-16 14:38:13 -04:00
Vitor Pamplona ece7194456 Updates all dependencies 2026-06-13 18:10:13 -04:00
Róbert NagyandGitHub e3441157ad Merge branch 'main' into feat/desktop-vlcj-to-kdroidfilter 2026-06-11 13:50:42 +03:00
nrobi144 704f4f44ee feat(desktop): replace vlcj with kdroidFilter ComposeMediaPlayer + JCodec/FFmpeg
Drops uk.co.caprica:vlcj 4.8.3 (GPL-3.0) from desktopApp and replaces it
with an MIT-dominant stack:

- Video / audio playback: io.github.kdroidfilter:composemediaplayer:0.10.0
  (MIT) — OS-native backends (Media Foundation on Windows, AVFoundation on
  macOS, GStreamer on Linux). First-class Compose VideoPlayerSurface.
- Thumbnail extraction: org.jcodec:jcodec(+javase):0.2.5 (BSD-2) primary
  H.264 path, raw ProcessBuilder FFmpeg fallback for HEVC / VP9 / AV1 /
  HLS / non-faststart MP4.
- Binary SPDX: MIT AND LGPL-2.1-or-later AND BSD-2-Clause AND Apache-2.0.
  rpmLicenseType updated accordingly (previously misdeclared as MIT
  while shipping GPLv3 vlcj).

Code changes:
- Deleted: VlcjPlayerPool, MacOsVlcDiscoverer, BundledVlcDiscoverer,
  VlcResourceResolver
- Rewrote: GlobalMediaPlayer (kdroidFilter engine + snapshotFlow-based
  state sync into the preserved MediaPlaybackState contract);
  VideoThumbnailCache (JCodec → ProcessBuilder ffmpeg cascade, with a
  hard 4 MiB download cap, Content-Type sniff to reject HTML error
  pages, and cleanup of zero-byte cache entries); DesktopVideoPlayer
  (mounts VideoPlayerSurface for the active URL, codec/network error
  UX with "Open in default player" fallback)
- Updated: NowPlayingBar + GlobalFullscreenOverlay to render
  VideoPlayerSurface directly (drops the videoFrame ImageBitmap relay)
- Main.kt: drops vlcj pre-init / shutdown calls (kdroidFilter lazy-loads
  natives + registers its own shutdown hook on Windows)

Build / packaging:
- Removes ir.mahozad.vlc-setup plugin + vlcSetup{} block + the per-OS
  bundled VLC tree + the -Dvlc.plugin.path JVM arg
- Adds NOTICE.md + per-component LICENSE-*.txt under appResources/common
  (LGPL-2.1 license text is a placeholder — replace with verbatim FSF
  text before release)
- Adds per-OS LGPL FFmpeg drop-in directories with README pointing at
  the recommended LGPL binary source (osxexperts.net / Crigges Windows
  LGPL build)
- Adds Flathub manifest skeleton (Gitnuro-style: org.freedesktop.Platform
  24.08 + openjdk21 extension + org.freedesktop.Platform.ffmpeg-full
  add-extension for patent codecs)
- AppRun: drops VLC LD_LIBRARY_PATH / VLC_PLUGIN_PATH env wiring

Verified on macOS arm64:
- ./gradlew :desktopApp:compileKotlin                BUILD SUCCESSFUL
- ./gradlew :desktopApp:test                         BUILD SUCCESSFUL
- ./gradlew :desktopApp:spotlessApply                clean
- Smoke launch: no VLC/vlcj/libvlc log lines, kdroidFilter native
  library extracts to ~/.cache/composemediaplayer/native/, thumbnail
  cache populates at ~/.cache/amethyst-desktop/video-thumbs/ with the
  4 MiB cap enforced
- H.264 MP4 playback (active + thumbnail extraction) confirmed
- VP9-in-WebM playback fails on macOS as AVFoundation cannot decode it —
  expected codec gap; surfaced via PlaybackErrorMessage + "Open in
  default player" handoff in DesktopVideoPlayer

Docs:
- docs/plans/2026-06-11-feat-replace-vlcj-with-kdroidfilter-plan.md
- docs/plans/2026-06-11-vlcj-replacement-testing-sheet.md
2026-06-11 13:37:33 +03:00
davotoula 190316eb28 Replace GPLv3 TarsosDSP with in-house pitch shifter 2026-06-09 21:59:50 +02:00
davotoula 8097e6c52d fix(math): upgrade LaTeX renderer to maintained rikkahub jlatexmath fork 2026-06-09 19:51:31 +02:00
nrobi144 e17f04eb54 build(commons,cli): add Thumbnailator + force AWT headless for image compression
Phase 0 of the desktop image compression plan
(docs/plans/2026-06-08-feat-desktop-image-compression-plan.md).

  - commons jvmMain gains net.coobird:thumbnailator:0.4.21 (pure-Java,
    MIT) — to be consumed by the new ImageReencoder in Phase 1.
  - amy CLI now sets -Djava.awt.headless=true via three paths so any
    transitive ImageIO/AWT touch never spawns a GUI thread:
      * applicationDefaultJvmArgs in cli/build.gradle.kts (covers the
        installDist startup scripts and any future jpackage launcher),
      * the amyImage custom Unix launcher in cli/build.gradle.kts,
      * System.setProperty as the first line of cli Main.kt — belt-
        and-braces for invocations that bypass the launcher scripts.
  - commons:jvmTest forces -Djava.awt.headless=true for the same
    reason during test runs.

Smoke tests (CompressionSmokeTest.kt) document Thumbnailator's
upscale-by-default behavior — ImageReencoder must gate the resize
itself in Phase 1.
2026-06-09 11:41:59 +03:00
Claude 1096647191 feat: render LaTeX math in notes with $...$ and $$...$$ delimiters
Posts that use the common dollar-delimiter convention (e.g. the
math-academy "Linear Independence" note) now render their formulas
as real equations instead of raw LaTeX text.

- commons: new MathParser tokenizes a line into atomic math spans
  (kept whole, since they contain spaces) interleaved with plain text,
  following the pandoc/remark-math dollar rules so currency like
  "$5 and $10" and escaped "\$" don't false-fire. New MathSegment
  carries the inner LaTeX + display flag through the rich-text pipeline.
- RichTextParser splits math out before the whitespace word-splitter
  when a line might contain math; non-math lines keep the existing path.
- amethyst: LatexEquation renders a MathSegment via JLaTeXMath, tinted
  to the current text color and sized to the font, with a raw-text
  fallback when the formula fails to parse. Wired into both the
  preview and no-preview render paths of RichTextViewer.

Scope: dollar delimiters only, regular (non-markdown) render path.

https://claude.ai/code/session_01N8ZhVv9912DLGNJiErVTR4
2026-06-08 14:47:56 +00:00
Vitor Pamplona dd92098343 updates all libs 2026-05-29 16:07:49 -04:00
Vitor Pamplona 5fce6764b5 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-26 18:48:06 -04:00
Claude ee0942d555 Merge remote-tracking branch 'origin/main' into claude/brave-clarke-hJ0PK 2026-05-26 15:32:47 +00:00
Claude e02972e1be build(commons): enable iosArm64 + iosSimulatorArm64 targets
Phase 2 task 10 of the iOS plan — flip on iOS targets for :commons.
Gradle dep resolution is fully green for iOS; actual Kotlin/Native
compilation runs on the macOS CI job (the dev container in which this
was authored can't extract the K/N LLVM toolchain).

Dep reshuffle to match what's actually KMP-available:

- commonMain: kept project(":quartz"), Compose Multiplatform, coil-compose,
  androidx-collection, kotlinx-collections-immutable, kotlinx-serialization-json,
  compose components-resources, androidx-lifecycle-viewmodel,
  androidx-lifecycle-runtime-compose. These all publish iosArm64 +
  iosSimulatorArm64 variants per `.module` inspection.

- jvmAndroid (NEW location): project(":nestsClient") (JVM+Android-only),
  coil-okhttp (JVM-only), markdown-commonmark / markdown-ui /
  markdown-ui-material3 (the RenderMarkdown.kt consumer is already in
  jvmAndroid), and androidx-lifecycle-viewmodel-compose (AndroidX publishes
  android + jvmStubs + linuxx64Stubs variants — no iOS, so the viewModel()
  Composable helper stays JVM-bound until we either swap to the
  org.jetbrains.androidx.lifecycle variant or accept a platform-specific
  ViewModel access pattern on iOS).

- libs.versions.toml: adds androidx-lifecycle-viewmodel catalog entry.

- New intermediate source set iosMain → both iosArm64Main and
  iosSimulatorArm64Main depend on it (clean place for iOS-only actuals
  when KmpLock, WeakReference, etc. get their iOS implementations).

- .github/workflows/build.yml: test-quartz-ios job now also runs
  :commons:compileKotlinIosArm64 + :commons:compileKotlinIosSimulatorArm64.
2026-05-25 00:13:13 +00:00
Claude 5c2f93f82f refactor: replace stately with LargeCache + KmpLock
Address review feedback: the project already has LargeCache (in quartz,
with jvmAndroid/appleMain/linuxMain actuals) as its KMP concurrent-map
abstraction — it's used pervasively in the model layer. Adding stately
duplicated that capability with an external dep.

- Comparable-key maps switch to LargeCache:
  * ChessEventCollector.moves (String key)
  * ChessEventCollectorManager.collectors (String key)
  * ChessRelayFetchHelper.events (String key)
  * ChessRelayFetchHelper.relayEventCounts: LargeCache<NormalizedRelayUrl,
    AtomicInt> with getOrCreate { AtomicInt(0) }.addAndFetch(1) — replaces
    the stately .block { compute } increment idiom. getOrCreate is atomic
    via ConcurrentSkipListMap.putIfAbsent so all threads end up
    incrementing the same AtomicInt instance.
  * ChessLobbyLogic.recentlyLoadedGames (String key)

- The SubscriptionManager pair (MutableComposeSubscriptionManager,
  ComposeSubscriptionManager) keeps a plain mutableMapOf — T :
  MutableQueryState is generic and not Comparable, so LargeCache's
  ConcurrentSkipListMap backing would ClassCastException at put time.
  Concurrency comes from a KmpLock-guarded map.

- Set-shaped uses switch to KmpLock + mutableSetOf:
  * ChessEventCollector.processedEventIds
  * ChessRelayFetchHelper.eoseReceived
  * ChessLobbyLogic.dismissedGameIds + seenEventIds (the bounded LRU
    keeps insertion-order eviction; mutableSetOf returns LinkedHashSet
    on every KMP target).

- UserRelaysCache.flow's lock: stately Lock -> KmpLock.

Adds expect class KmpLock() with jvmAndroid actual that wraps
ReentrantLock. iOS actual (NSLock) will land with the iOS target.
Mirrors the WeakReference pattern from the previous PR.

Drops stately-concurrent-collections 2.1.0 from libs.versions.toml and
commons/build.gradle.kts (no remaining consumers).
2026-05-24 21:10:35 +00:00
Claude bf6467cdcf refactor: drop ConcurrentHashMap from commonMain
Phase 2 of the iOS plan — clears the ConcurrentHashMap blockers from
commons/commonMain. Five files migrated (the four flagged in the
initial audit + ChessLobbyLogic, which used fully-qualified inline
java.util references that the import-based audit missed).

Adds co.touchlab:stately-concurrent-collections 2.1.0 — a small,
mature KMP library that provides ConcurrentMutableMap /
ConcurrentMutableSet with semantics equivalent to ConcurrentHashMap /
ConcurrentHashMap.newKeySet on every Kotlin target. The .block { }
helper covers the compound-update paths (ChessRelayFetchHelper's
per-relay event-count compute, ChessLobbyLogic's bounded-LRU dedup).

- ComposeSubscriptionManager + MutableComposeSubscriptionManager:
  ConcurrentHashMap -> ConcurrentMutableMap
- ChessEventCollector + ChessEventCollectorManager: map and Set
- ChessRelayFetchHelper: in-function event/relay state
- ChessLobbyLogic: replaces dismissedGameIds (synchronizedSet),
  recentlyLoadedGames (ConcurrentHashMap), seenEventIds (bounded LRU
  using LinkedHashSet via Collections.synchronizedSet + synchronized {}).
  seenEventIds keeps insertion-order eviction semantics because
  mutableSetOf returns LinkedHashSet on every KMP target.
2026-05-24 18:24:22 +00:00
Claude 17cee60aac feat(amethyst): expose searchProfiles to Gemini via androidx.appfunctions
First Phase 2 verb wired through to the Android App Functions runtime so
Gemini (and other system agents) can drive Amethyst.

Scope is intentionally narrow:
  * One read-only verb (searchProfiles), built on top of the existing
    SearchActions in commons. No write verbs yet — they need a story
    for NIP-46 / NIP-55 signer prompts from a background dispatcher.
  * Play channel only. appfunctions 1.0.0-alpha09 is a Google AI alpha;
    F-Droid builds continue to ship without any Google AI dependencies.

Architecture:
  * AmethystAppFunctions — plain Kotlin host with @AppFunction methods.
    The KSP-driven appfunctions-compiler discovers them and generates
    the dispatch metadata XML at build time.
  * PlayAmethyst — play-only Application subclass implementing
    AppFunctionConfiguration.Provider; supplies the factory the
    library uses to construct the host class. Manifest replaces
    android:name in the play flavor only; F-Droid keeps the unmodified
    Amethyst class.
  * The androidx-provided PlatformAppFunctionService is registered in
    the play manifest as the bind point — Amethyst doesn't ship a
    custom Service.

KSP is now a project-wide plugin (apply false at the root); applied in
amethyst/ to run the appfunctions-compiler over the play sourceSet.

Amethyst becomes `open class` so PlayAmethyst can extend it. No other
behavior change.
2026-05-24 18:23:59 +00:00
Vitor Pamplona a9306dcea5 update dependencies 2026-05-23 17:42:26 -04:00
Vitor Pamplona e0abc4a224 v1.11.0 2026-05-20 18:59:05 -04:00
Vitor Pamplona 280f21159f v1.10.0 2026-05-16 16:53:00 -04:00
Vitor Pamplona 8a498695a9 v1.09.2 2026-05-16 12:06:32 -04:00
Vitor Pamplona 5a30b10a77 v1.09.1 2026-05-15 18:25:17 -04:00
Vitor Pamplona 74c0d6906e v1.09.0 2026-05-15 15:43:47 -04:00
Vitor Pamplona d60dcf9c79 update dependencies 2026-05-14 12:06:55 -04:00
Vitor Pamplona 6ab077361f updates dependencies 2026-05-12 22:14:06 -04:00
davotoula 91aa72ae1e Rip DLNA code paths, collapse to Chromecast-only
- chore(cast): drop protocol-toggle settings, strings, perms, deps
- gate cast on play flavor via BuildConfig.IS_CASTING_AVAILABLE
- fix(cast): keep local player paused across recompose; disable transport while casting
2026-05-11 16:35:47 +02:00
Claudeanddavotoula 5df0e2a37a Add LAN video casting (Chromecast on play, DLNA on both flavors) v1
fix(cast): handle every Chromecast session terminal state and HLS mime hint
fix(cast): repair DLNA Res ctor and add play-only protocol toggle
fix(cast): backfill Cast button for accounts with pre-existing video settings
fix(cast): include cast glyphs in symbol font subset and add diagnostic logs
fix(cast): wire jUPnP's required Jetty deps and survive dialog dismissal
fix(cast): add jetty-client so jUPnP can issue UPnP control requests
2026-05-11 16:35:47 +02:00
Vitor Pamplona faf04fa1e3 update dependencies 2026-05-08 17:06:28 -04:00
Claude e214143def feat(relay): TOML config file (--config /path/to/relay.toml)
Adds operator-facing TOML configuration to :quartz-relay, with the
section layout deliberately mirroring nostr-rs-relay's config.toml so
existing operators can port across with little churn.

Sections parsed AND enforced today:
  [info]      — NIP-11 doc fields (name, description, contact, pubkey,
                 software, supported_nips, …); replaces the previous
                 hardcoded RelayInfo.default()
  [network]   — host, port, path
  [database]  — in_memory toggle + file path
  [options]   — verify_signatures, require_auth (compose to the right
                 IRelayPolicy stack)

Sections parsed today but NOT YET ENFORCED (forward-compat for the
upcoming rate-limit / authorization work — relay logs a warning when
they're set):
  [limits]         — max_event_bytes, messages_per_sec, …
  [authorization]  — pubkey_whitelist/blacklist, kind_whitelist/blacklist
  [options].reject_future_seconds
  [network].remote_ip_header

CLI flag precedence over the config file is preserved: --host, --port,
--path, --info, --db, --auth, --verify all override the matching field.

Adds:
  - cc.ekblad:4koma 1.2.0 for TOML parsing
  - quartz-relay/config.example.toml as the canonical operator reference
  - 5 unit tests (defaults, full parse, NIP-11 mapping, bundled example
    file, optional sections)
2026-05-07 01:21:58 +00:00
Claude eb80dbcd15 feat(quartz-relay): rename + promote to a real Nostr relay
Renames :quartz-test-relay to :quartz-relay and promotes it from a
test-only fixture to a real, runnable Nostr relay that just happens
to also be used in tests.

Two transports now share the same `Relay` core:

- `RelayHub` + `InProcessWebSocket` — no socket, fastest path; ideal
  for unit tests inside one JVM.
- `LocalRelayServer` — Ktor `embeddedServer` (CIO engine) listening
  on a real `ws://` port. Use for `cli` interop tests, Android
  instrumented tests, or running the relay standalone.

NIPs implemented (all driven through production `NostrClient` in the
new `LocalRelayServerTest`):

- NIP-01 wire protocol (REQ/EVENT/EOSE/CLOSE) over real WebSockets
- NIP-09 deletion (via existing `DeletionRequestModule`)
- NIP-11 relay info doc — `RelayInfo` wraps the existing
  `Nip11RelayInformation` model and is served on HTTP GET when
  `Accept: application/nostr+json` is requested. Loadable from a
  JSON config file via `RelayInfo.fromFile(...)`.
- NIP-40 expiration (existing `ExpirationModule`)
- NIP-42 AUTH (existing `FullAuthPolicy`, opted-in via the
  `--auth` flag in the standalone runner)
- NIP-45 COUNT
- NIP-50 search via the existing FTS index
- NIP-62 right-to-vanish (existing module)

Adds a standalone runner: `./gradlew :quartz-relay:run --args="--port 7447 --verify"`
binds the relay to a real port. Flags: --host, --port, --path,
--info <file>, --db <file>, --auth, --verify.

Test-only event generators moved to a clearly-named
`com.vitorpamplona.quartz.relay.fixtures` package so they're still
shareable with consumer tests without polluting the production API.

Adds `LocalRelayServerTest` covering NIP-01/11/42/45/50 over a real
loopback WebSocket. Existing 11-test `Nip01ComplianceTest` (in-process
transport) continues to pass.
2026-05-07 00:56:57 +00:00
Vitor Pamplona 5bf542c18d Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-05-06 10:55:17 -04:00
Vitor Pamplona 85d27a80b0 Updates AGP 2026-05-06 10:53:14 -04:00
Claude 614e403037 chore(deps): bump schnorr256k1-kmp to 1.0.5 2026-05-06 14:31:28 +00:00
Claude ecb985e2b4 build(quartz): bump schnorr256k1-kmp to 1.0.3 with bundled JVM JNI
Pulls in the per-platform JNI shards (linux-x86_64, linux-aarch64,
darwin-x86_64, darwin-aarch64) that the upstream library now publishes as
runtime dependencies of schnorr256k1-kmp-jvm. The 1.0.3 release also
moved the groupId to com.vitorpamplona.schnorr256k1.

Net effect: Secp256k1CrossValidationTest's customCImplementationMatchesAcinqAndKotlin
no longer prints SKIP, and the Custom C(JNI) column in
Secp256k1TripleBenchmark now reports numbers (verifySchnorr ~25.6k ops/s,
signSchnorr cached pk ~57.4k ops/s) instead of (N/A). 188/188 tests pass
in the secp256k1 suite on a stock JVM with no LD_LIBRARY_PATH or
-Djava.library.path needed.

https://claude.ai/code/session_01XEfLBStDum7h8Brwo7qFXt
2026-04-28 20:20:28 +00:00
Claude 2ad1a48123 refactor(quartz): migrate in-tree C secp256k1 to libschnorr256k1-kmp
The custom C secp256k1 implementation under quartz/src/main/c/ was never
wired into Gradle (no externalNativeBuild block) and only existed for the
3-way benchmark + cross-validation test against ACINQ on JVM/Android.
The C library has been split out to vitorpamplona/libschnorr256k1{,-kmp},
so the in-repo copy is dead weight.

This change replaces it with the published Maven Central artifact
`com.vitorpamplona:schnorr256k1-kmp:1.0.0`, scoped to test/benchmark
configurations only — production crypto continues to use ACINQ
secp256k1-kmp on JVM/Android and the pure-Kotlin Secp256k1 on native.

The Android AAR ships libschnorr256k1_jni.so for arm64-v8a and x86_64,
so the Android benchmark now exercises the C row automatically (no more
manual build_android.sh). On JVM the .so still has to be installed by the
developer; the cross-validation test and triple-benchmark gracefully skip
the C row when System.loadLibrary fails, matching prior behavior.

Verified on JVM: all 13 secp256k1 jvmTest classes pass (188 tests), and
ACINQ vs pure-Kotlin benchmark numbers match the documented baseline
within sandbox noise (verifySchnorr ~15k ops/s, signSchnorr cached
~33k ops/s, pubkeyCreate+Compress ~36k ops/s).

Removes ~5,100 LOC: quartz/src/main/c/ (4,500), Secp256k1InstanceC.*
expect/actual shim (560), Secp256k1C JNI declaration object.

https://claude.ai/code/session_01KnvpK2amcVZKfFiZJvHjVe
2026-04-28 15:36:25 +00:00
Vitor Pamplona af6b8c8a52 Removes old MLKit labeler because of the terrible labels it was creating. 2026-04-27 11:29:28 -04:00
Claude 952e0a2192 feat(ai): prefer genai image description, fall back to image labeling
Adds com.google.mlkit:genai-image-description as the primary alt-text
source — Gemini Nano via AICore produces full descriptive sentences on
supported devices. When checkFeatureStatus reports anything other than
AVAILABLE (or AICore is missing), the service falls back to the legacy
play-services-mlkit-image-labeling keyword join. Both paths sit behind
the same MLKitImageLabelService.suggestAltText API; the F-Droid stub is
unchanged.
2026-04-27 13:32:57 +00:00