Commit Graph

11567 Commits

Author SHA1 Message Date
Crowdin Bot 1d4f0ebb59 New Crowdin translations by GitHub Action 2026-04-17 12:21:30 +00:00
dependabot[bot] f098fb4cdc chore(actions): bump the actions group with 4 updates
Bumps the actions group with 4 updates: [actions/upload-artifact](https://github.com/actions/upload-artifact), [actions/github-script](https://github.com/actions/github-script), [nick-fields/retry](https://github.com/nick-fields/retry) and [softprops/action-gh-release](https://github.com/softprops/action-gh-release).


Updates `actions/upload-artifact` from 6 to 7
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

Updates `actions/github-script` from 7 to 9
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v9)

Updates `nick-fields/retry` from 3.0.2 to 4.0.0
- [Release notes](https://github.com/nick-fields/retry/releases)
- [Commits](https://github.com/nick-fields/retry/compare/ce71cc2ab81d554ebbe88c79ab5975992d79ba08...ad984534de44a9489a53aefd81eb77f87c70dc60)

Updates `softprops/action-gh-release` from 2.6.2 to 3.0.0
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/3bb12739c298aeb8a4eeaf626c5b8d85266b0e65...b4309332981a82ec1c5618f44dd2e27cc8bfbfda)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: nick-fields/retry
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
- dependency-name: softprops/action-gh-release
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-17 12:20:54 +00:00
Vitor Pamplona f05fda1485 Merge pull request #2428 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-17 08:19:51 -04:00
Vitor Pamplona f5f8bfd7dc Merge pull request #2430 from nrobi144/feat/desktop-multiplatform-distribution
feat(desktop): multi-platform distribution — 8 assets, Homebrew + Winget
2026-04-17 08:19:40 -04:00
nrobi144 ac349665eb fix(desktop): review fixes for workspace management UX
- Remove consumed flag (stale on re-open, double-fire is harmless)
- Fix keyboard nav for WORKSPACES tab (was broken, only worked for screens)
- Reset selectedIndex on tab switch
- Fix delete active workspace → reload columns for new active workspace
- Extract DeckColumnType.param() extension to eliminate 3x duplication
- Simplify moveSelection to handle all modes (tabs + unified search)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:55:25 +03:00
nrobi144 09f76f036a feat(desktop): workspace management UX with tabs, editor, unified search
Add two-tab App Drawer (Screens/Workspaces), workspace cards with CRUD,
editor dialog with icon picker and column configuration, unified search
across screens and workspaces, layout mode auto-switching, and
Cmd+Shift+S to save current layout as workspace.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 14:47:24 +03:00
nrobi144 5b8ddf0a20 fix(desktop): review fixes for navigation overhaul
- Fix right-click detection: use isSecondaryPressed (not button.index==2)
- Fix parseColumnType missing drafts/highlights/editor/article cases
- Fix param extraction for Editor.draftSlug and Article.addressTag
- Fix deleteWorkspace index correction logic

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:47:17 +03:00
nrobi144 e528656ebd feat(desktop): workspace system with save/switch/restore (v1c)
Add workspace presets for different usage modes. Each workspace stores
layout mode + column configuration. Switching destroys current layout
and rebuilds from saved config (no background state).

- Add Workspace data model with WorkspaceColumn
- Add WorkspaceManager with save/load/switch/add/delete
- Add WorkspaceIcons registry for Material icon resolution
- Add WorkspaceBar to AppDrawer footer with workspace chips
- Add DeckState.loadFromWorkspace() for column rebuilding
- Add workspaces persistence to DesktopPreferences
- Default workspace: "Social" (Home + Notifications + DMs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:42:30 +03:00
nrobi144 28e9b8202f feat(desktop): customizable nav bar with pin/unpin (v1b)
Add PinnedNavBarState to manage user-customizable sidebar items.
Replace hardcoded navItems in SinglePaneLayout with pinnedScreens
from state, persisted as CSV to DesktopPreferences. Right-click
context menu on App Drawer items to pin/unpin from sidebar.

- Add PinnedNavBarState with pin/unpin/move/save/load
- Add pinnedNavItems to DesktopPreferences (CSV persistence)
- Update SinglePaneLayout to use pinnedScreens from state
- Update AppDrawer with isPinned indicator and context menu
- Remove NavItem data class and hardcoded navItems list
- HomeFeed and Settings are always pinned (non-removable)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:37:06 +03:00
nrobi144 e79096f16b feat(desktop): add App Drawer with categorized screen launcher
Replace AddColumnDialog with full-screen App Drawer overlay (Cmd+K).
Screens organized by category (Social, Long-Form, Discovery, Identity,
Play) with instant search, keyboard navigation (arrows + Enter), and
open-column indicators in Deck mode. Works in both Single Pane and
Deck layout modes.

- Add AppDrawer.kt with ScreenCategory, LAUNCHABLE_SCREENS registry
- Add SinglePaneState for hoisted single-pane navigation
- Wire Cmd+K shortcut, redirect Cmd+T to drawer
- Add "More" button to SinglePaneLayout nav rail
- Delete AddColumnDialog.kt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-17 13:19:39 +03:00
nrobi144 54dffddf2f chore: remove plan doc from PR (local artifact) 2026-04-17 11:32:31 +03:00
nrobi144 84c4b461a9 fix(release): address code review findings (P1-P3)
P1 fixes (must-fix):
- AppRun: VLC path corrected to usr/lib/app/linux/vlc (jpackage actual
  path), add VLC_PLUGIN_PATH env var for codec discovery
- linuxdeploy: use APPIMAGE_EXTRACT_AND_RUN=1 env var to bypass FUSE
  on CI runners instead of fragile pre-extract + symlink approach
- Prerelease classifier: inverted to positive-match stable format
  (^v[0-9]+\.[0-9]+\.[0-9]+$) across desktop, Android, and composite
  action — eliminates regex drift between allowlists
- assert-stable-release: now accepts inputs (tag, is_prerelease,
  is_draft) so workflow_dispatch on bump workflows also validates

P2 fixes (should-fix):
- .gitignore: add linuxdeploy-*.AppImage and extracted dirs
- RPM version: use replace("-", "~") instead of substringBefore("-")
  for correct RPM prerelease ordering (1.08.0~rc1 < 1.08.0)
- linux-portable → linux alias moved into collect_assets() in
  scripts/asset-name.sh (single source of truth)
- Android cache key: add gradle/libs.versions.toml to hashFiles
- r0adkll/sign-android-release: SHA-pinned to 349ebdef (v1)

P3 fixes (nice-to-have):
- LINUXDEPLOY_OUTPUT_VERSION env var replaced with
  APPIMAGE_EXTRACT_AND_RUN (misleading comment + redundant var)
- nick-fields/retry timeout: 40m → 15m (surface real hangs)
- generate_release_notes: true only on Android job (avoid
  last-writer-wins race across 6 concurrent uploaders)
- apt-get install rpm: tightened guard to matrix.family == 'linux'
  (linux-portable leg doesn't need rpm tooling)
2026-04-17 11:03:01 +03:00
Crowdin Bot 2be1036cd0 New Crowdin translations by GitHub Action 2026-04-17 01:16:22 +00:00
Vitor Pamplona a525ebabd5 Merge pull request #2429 from vitorpamplona/claude/redesign-longform-header-ajshi
Enhance long-form article preview with metadata and topics
2026-04-16 21:14:39 -04:00
Claude 71537a8950 feat: redesign LongFormHeader with richer article card layout
- 16:9 cover image with rounded top corners
- Topic/hashtag chips row above title (up to 3)
- Bolder titleLarge headline, max 2 lines
- Gray summary with max 3 lines
- Author row with display name, time ago, and reading-time badge
2026-04-16 22:05:03 +00:00
Crowdin Bot 6ff1a7ef8f New Crowdin translations by GitHub Action 2026-04-16 21:19:11 +00:00
Vitor Pamplona f0730ce4f7 Merge pull request #2427 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-16 17:17:52 -04:00
Crowdin Bot 55ef3e89f6 New Crowdin translations by GitHub Action 2026-04-16 21:17:27 +00:00
Vitor Pamplona 449c052cb9 Spotless Apply 2026-04-16 17:16:56 -04:00
Vitor Pamplona cb98961f87 Merge pull request #2425 from vitorpamplona/claude/fix-call-ringing-lifecycle-ukm5g
Refactor call session lifecycle to Activity-owned CallSession
2026-04-16 17:15:56 -04:00
Claude bc424acfcf fix: pass Activity context (not applicationContext) to CallSession
WebRTC's Camera2Session accesses WindowManager for device orientation.
WindowManager is a visual service that requires an Activity context —
applicationContext throws IllegalAccessException on Android 12+.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 20:02:30 +00:00
Claude 7e93f82577 fix(audio): switch ringtone from Ringtone to MediaPlayer + idempotent start
Root cause of "ringing survives reject on old Android":

1. startRinging() was NOT idempotent. When CallManager re-emitted
   IncomingCall state (e.g., when another group member rejected while
   still ringing), the state collector called startRinging() again.
   startRingtone() OVERWROTE the ringtone field reference without
   calling stop() on the old one. Old Ringtone instances kept
   playing with no reachable reference — stopRingtone() on reject
   only stopped the latest one.

2. Ringtone.isLooping was only set on API 28+. On older Android the
   Ringtone class had documented reliability problems with stop().
   MediaPlayer has reliable stop/release on all API levels and
   supports looping universally.

Fixes:

- Switch from android.media.Ringtone to android.media.MediaPlayer.
- Make startRinging() idempotent — always call stopRinging() first
  so any existing player/vibrator is torn down before a new one
  starts.
- Add aggressive tracing logs throughout the reject path:
  * CallAudioManager: instance id + thread on every start/stop,
    MediaPlayer error listener, before-and-after player hashes.
  * CallSession: log every state collector tick, log close() entry.
  * CallManager: log rejectCall() entry/exit + transitionToEnded.
  * CallNotificationReceiver: log every action with state transitions.

With these logs, if ringing still survives reject we can trace
exactly which primitive is failing.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 19:51:25 +00:00
davotoula e4d17afcc5 fix: use _sessionEvents.tryEmit instead of recursive self-call in emitSessionEvent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:56:46 +00:00
davotoula 3e0d79a091 spotless: fix formatting violations in call lifecycle files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 18:56:46 +00:00
Claude 29c5c26af7 fix: restore onDestroy hangup, prevent event drops, fix deadlock risk
Three fixes from audit:

1. PiP swipe-away regression (Bug 5): Restored hangup signaling in
   CallActivity.onDestroy via detached CoroutineScope. This is the
   PRIMARY signaling path for PiP dismiss, back press, and finish().
   CallForegroundService.onTaskRemoved is the BACKUP for task swipe
   from Recents. Both paths are idempotent — double-publishing is safe.

2. SharedFlow event drops (Bug 1): Increased sessionEvents buffer from
   16 to 256 to handle worst-case ICE candidate bursts. Added
   emitSessionEvent() helper that logs drops as errors. Increased
   renegotiationEvents buffer from 8 to 32. tryEmit is kept (not
   emit) to avoid suspending while holding stateMutex, which would
   block all signaling.

3. runBlocking deadlock risk (Bug 2): CallSessionBridge.clear() now
   uses CallManager.reset() (non-blocking, no mutex) instead of
   runBlocking { hangup() }. The bridge teardown is for local state
   cleanup during account switch — hangup signaling is the Activity's
   and Service's responsibility.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:46 +00:00
Claude 89cf5e14d4 fix: move hangup signaling from fire-and-forget scope to CallForegroundService
The previous approach used a detached CoroutineScope in
CallActivity.onDestroy to publish hangup/reject events. This was
unreliable: the process could be killed before the coroutine completed,
leaving the remote peer's phone ringing.

Fix: CallForegroundService now owns hangup signaling via two paths:

1. onTaskRemoved() — fires when user swipes app from Recents. Uses
   runBlocking with 3-second timeout to synchronously publish hangup
   before the service is killed.

2. onDestroy() — fires when the service is stopped for any other
   reason. Same runBlocking pattern as a safety net.

Changes:
- AndroidManifest: stopWithTask="false" so onTaskRemoved fires instead
  of the service being silently killed.
- CallForegroundService: added onTaskRemoved(), onDestroy(), and
  publishHangupBlocking() helper.
- CallActivity.onDestroy: removed fire-and-forget CoroutineScope.
  Session resource cleanup (close()) still happens here; signaling
  hangup is delegated to the foreground service.
- CallSessionBridge.clear(): converted fire-and-forget hangup to
  runBlocking with 3-second timeout for account-switch safety.

The three-layer hangup guarantee is now:
1. CallForegroundService.onTaskRemoved/onDestroy — synchronous,
   reliable (runBlocking).
2. CallManager ringing watchdog (65s) — if nothing else fires.
3. Remote peer's own timeout — last resort.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:46 +00:00
Claude ac6d8eb417 refactor: replace CallManager mutable callbacks with SharedFlow
Convert all 5 mutable `var` callback fields on CallManager
(onAnswerReceived, onIceCandidateReceived, onNewPeerInGroupCall,
onMidCallOfferReceived, onPeerLeft) into a single
`sessionEvents: SharedFlow<CallSessionEvent>`.

Benefits:
- Eliminates the stale-callback window between Activity destroy and
  recreate. No more nulling callbacks in onDestroy — the `closed`
  flag on CallSession is sufficient.
- No mutable state shared between Activity and background code.
- CallActivity no longer wires or tears down callbacks — CallSession
  subscribes to the flow in its init block.
- Type-safe sealed interface (CallSessionEvent) replaces 5 separate
  lambda types.

The renegotiationEvents SharedFlow remains separate because
renegotiation has its own glare-resolution logic in CallSession.

Updated all 11 test sites in CallManagerTest to collect from
sessionEvents instead of setting callback vars.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:45 +00:00
Claude 980515c0cc fix: add closed guards to WebRTC callbacks, connecting watchdog, account-switch safety
Three additional hardening fixes:

1. WebRTC callback guards: Every callback passed to WebRtcCallSession
   (onIceCandidate, onPeerConnected, onRemoteVideoTrack, onDisconnected,
   onError, onRenegotiationNeeded, onIceRestartOffer) now checks `closed`
   before touching any session resource. Prevents native crashes from
   libwebrtc callbacks firing after close() has disposed PeerConnections.

2. Connecting-state watchdog: New 30-second timer armed when entering
   Connecting state, disarmed on Connected/Ended/reset. If ICE
   negotiation hangs (broken TURN, restrictive NAT), the call ends
   with TIMEOUT instead of leaving the user on a "Connecting..." screen
   forever.

3. Account-switch safety: CallSessionBridge.clear() now calls
   callManager.hangup() before nulling references. Prevents a stale
   CallSession from invoking signing/publishing lambdas on a disposed
   Account after logout or account switch.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:45 +00:00
Claude 8075579168 fix: address race conditions in CallSession close/callback lifecycle
Fixes found by audit:

1. Set `closed = true` at the top of close() so the init collectors
   (state, renegotiation, proximity) short-circuit when they see
   the flag. Prevents them from calling methods on disposed resources
   during the window between close() and lifecycleScope cancellation.

2. Null out all CallManager callbacks in onDestroy after close() so
   signaling events arriving between Activity destroy and recreate
   don't route to a dead CallSession.

3. Synchronize peerSessionMgr.disposeAll() + reassignment in close()
   to prevent concurrent collector reads on a half-disposed manager.

4. Reorder onDestroy: close() runs first (synchronous resource
   release), then callback nulling, then best-effort hangup signaling.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:45 +00:00
Claude 3cddda6ef9 fix: CallActivity owns the entire call session lifecycle
Root cause: WebRTC call ringing continued forever after cancellation
because ringing, WebRTC, and notifications were owned by CallController
which lived in viewModelScope (account lifetime), not by CallActivity.
Cleanup depended on a state collector observing Ended in time, which
raced, dropped, and went stale.

Architecture after fix:
- CallManager (background, AccountViewModel scope): state machine only.
  No audio, no WebRTC, no notifications. Exposes state as StateFlow,
  renegotiation events as SharedFlow, and a 65-second ringing watchdog
  as a fail-safe.
- CallSession (Activity-scoped, replaces CallController): owns all call
  resources. Created in onCreate, destroyed via close() in onDestroy.
  Implements AutoCloseable. No cleanedUp guard flag needed.
- CallSessionBridge: slimmed down — holds only callManager +
  accountViewModel for cross-Activity reference sharing. No
  callController field.

Key changes:
- CallController renamed to CallSession, moved to ui/call/session/.
- CallSession.close() replaces cleanup() — single-shot, no guard flags.
- CallActivity creates and owns CallSession directly.
- CallManager.onRenegotiationOfferReceived callback replaced with
  renegotiationEvents SharedFlow (buffered, survives Activity restarts).
- CallManager gains ringing watchdog (65s) that forces Ended(TIMEOUT)
  if state stays in IncomingCall/Offering past the deadline.
- CallNotifier extracted from NotificationUtils into dedicated class.
- AccountViewModel.initCallController deleted; callManager wired to
  newNotesPreProcessor eagerly in init block.
- ChatroomScreen uses CallActivity.launchForOutgoingCall() with intent
  extras instead of directly calling callController.initiateGroupCall().
- AndroidManifest adds navigation|fontScale|density to configChanges
  so no config change recreates CallActivity mid-call.

Invariants:
1. CallActivity.onDestroy → session.close() → all resources released.
2. Ringing cannot outlive any code path: Activity close, state
   collector, and 65s watchdog provide three independent stop paths.
3. Config changes cannot kill the call (manifest prevents recreation).
4. No global mutable singleton for CallController.

https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54
2026-04-16 18:56:45 +00:00
Vitor Pamplona 8466a0c0d5 fixes compilation errors 2026-04-16 09:15:23 -04:00
Vitor Pamplona fb9171fb9a spotless 2026-04-16 08:51:15 -04:00
Vitor Pamplona 42cd1cdf0c Merge pull request #2423 from vitorpamplona/claude/add-discovery-articles-screen-IRDoN
Add Articles feed screen with NIP-23 long-form content support
2026-04-16 08:50:35 -04:00
Vitor Pamplona fbbe93aa3b Merge pull request #2422 from vitorpamplona/claude/add-discovery-products-screen-1hsvy
Add Products feed screen with classified listings support
2026-04-16 08:50:16 -04:00
Vitor Pamplona 6e7849eae7 Merge pull request #2424 from vitorpamplona/claude/add-marmot-media-upload-d0nNK
Implement MIP-04 encrypted media support for Marmot groups
2026-04-16 08:50:06 -04:00
Vitor Pamplona 6eff64f589 Merge pull request #2421 from vitorpamplona/claude/replace-mls-groups-fab-waio7
Add Marmot group messages to notifications feed
2026-04-16 08:43:51 -04:00
davotoula f41c062be4 lightcompressor-enhanced patch upgrade, fix for gif2mp4 2026-04-16 14:33:47 +02:00
nrobi144 c428661601 feat(release): expand desktop distribution to 8 assets + 2 package managers
Phases 3, 4, 5, 6 of the multi-platform distribution plan.

## create-release.yml rewrite
- Replace deprecated actions/create-release@v1 + upload-release-asset@v1 with
  softprops/action-gh-release@v2 (SHA-pinned)
- Expand build-desktop matrix: macos-13 (Intel), macos-14 (ARM),
  windows-latest, ubuntu-latest × 2 legs (deb+rpm, AppImage+tar.gz)
- Each matrix job uploads directly to release (no artifact round-trip — saves
  ~10 min + 1.5GB transfer per run)
- Inline portable archives: Windows .zip via 7z, Linux .tar.gz via tar
- linuxdeploy SHA-verified fetch for AppImage builds (not `continuous` tag)
- Per-asset size budget: hard fail at 1 GB per asset
- prerelease inferred from tag regex (-rc|-beta|-alpha|-dev|-snapshot)
- workflow_dispatch dry_run input: builds all assets without publishing,
  skips Android + bump workflows
- Tag-vs-libs.versions.toml assertion as first step in each matrix job
- Android + Quartz jobs preserved; migrated to softprops/action-gh-release@v2

## Package manager bump workflows (Homebrew + Winget)
- .github/workflows/bump-homebrew.yml — macauley/action-homebrew-bump-cask
  on ubuntu-latest (saves macOS runner quota). Cask name: `amethyst-nostr`.
- .github/workflows/bump-winget.yml — vedantmgoyal9/winget-releaser on
  windows-latest. PackageIdentifier: `VitorPamplona.Amethyst`.
- Shared composite action .github/actions/assert-stable-release rejects
  draft/prerelease/malformed-tag releases at action boundary (defense in
  depth vs workflow-level `if:` alone).
- Both workflows auto-open `release-ops`-labeled GH Issues on failure.
- Concurrency groups per tag prevent re-fire races.
- AUR + Scoop deferred to follow-up PR (unresolved ownership questions).

## Documentation
- BUILDING.md: per-platform build commands, asset naming contract, release
  runbook, bootstrap runbook, troubleshooting, uninstall paths, incident
  response, fallback plans (macos-13 retirement, Homebrew Sept 2026 deadline)
- README: expanded Download section with per-OS install matrix for 7 formats
  + 2 package managers. Deploying section points at BUILDING.md.

## Supply chain
- .github/dependabot.yml: monthly bumps for github-actions ecosystem
- All new third-party actions SHA-pinned:
  - softprops/action-gh-release v2.6.2
  - macauley/action-homebrew-bump-cask v4.0.0
  - vedantmgoyal9/winget-releaser v2
  - nick-fields/retry v3.0.2
- linuxdeploy binary SHA256-verified against pinned release tag
2026-04-16 14:54:51 +03:00
nrobi144 da1037423c feat(desktop): version source-of-truth + RPM + AppImage packaging
Phase 1+2 of multi-platform distribution plan:
- gradle/libs.versions.toml: add `app = "1.08.0"` as single source of truth
- Root build.gradle: allprojects { version = libs.versions.app.get() }
- amethyst/build.gradle: versionName from catalog (versionCode stays local)
- desktopApp/build.gradle.kts: drop hardcoded "1.0.0"; inherit project.version;
  add TargetFormat.Rpm; add linux DSL (menuGroup, appCategory, debMaintainer,
  rpmLicenseType, rpmPackageVersion with dashes stripped)
- desktopApp/build.gradle.kts: new createReleaseAppImage task wrapping
  createReleaseDistributable with linuxdeploy (TargetFormat.AppImage is
  broken in Compose 1.10.x — CMP-7101)
- packaging/appimage/: AppRun launcher (sets LD_LIBRARY_PATH for bundled VLC),
  amethyst.desktop XDG entry, 512x512 icon extracted from icon.icns
- scripts/asset-name.sh: single source for release asset naming contract
2026-04-16 14:35:01 +03:00
Claude 403ce57b79 feat: Add standalone Products screen with own filters and Around Me default
Extracts the Marketplace/Products functionality from the Discovery tab
into a dedicated standalone screen following the Pictures/Polls pattern.
The screen displays NIP-99 classifieds in a 2-column grid with its own
filter system, defaulting to Around Me (geolocation-based) filtering.

https://claude.ai/code/session_01PyrUZzSj7A3ZXucs7DTYbB
2026-04-16 03:28:16 +00:00
Claude 3d5366e608 feat: add standalone Articles screen for long-form content
Add a new dedicated Articles screen accessible from the left drawer that
displays long-form content (NIP-23, kind 30023) with its own filters,
data sources, and FAB. The screen follows the same pattern as Pictures,
Polls, and Longs screens. Default filter is set to "All Follows".

- New route: Route.Articles with navigation and drawer entry
- ArticlesFeedFilter using LongTextNoteEvent with own follow list
- ArticlesFilterAssembler/SubAssembler reusing makeLongFormFilter
- ArticlesScreen with DisappearingScaffold, top bar filter, and FAB
- ArticlesFeedLoaded rendering via ChannelCardCompose/LongFormHeader
- Account settings for defaultArticlesFollowList with persistence

https://claude.ai/code/session_01QnP527Zq3xrtcLDmdx3LNf
2026-04-16 03:24:01 +00:00
Claude 5f5d576f1e feat: implement MIP-04 encrypted media upload for Marmot groups
Adds full MIP-04 (Encrypted Media) support for Marmot group chats:

Protocol layer (quartz):
- Mip04MediaEncryption: ChaCha20-Poly1305 AEAD with MLS exporter key
  derivation (HKDF-Expand), random nonces, and AAD binding
- Mip04Cipher/Mip04NostrCipher: NostrCipher adapters for the existing
  EncryptedBlobInterceptor download-and-decrypt pipeline
- Mip04IMetaTag: NIP-92 imeta tag builder/parser with MIP-04 fields
  (url, m, filename, x, n, v=mip04-v2)
- MlsGroupManager.mediaExporterSecret(): MLS-Exporter("marmot",
  "encrypted-media", 32)

Upload flow (amethyst):
- MarmotFileUploader: per-file Mip04NostrCipher → existing
  UploadOrchestrator.uploadEncrypted pipeline (compression, stripping,
  Blossom upload)
- MarmotFileSender: builds imeta tags and sends kind:9 inner events
  through the MLS group message pipeline
- MarmotGroupMessageComposer: adds SelectFromGallery leading icon and
  ChatFileUploadDialog (mirrors NIP-17 DM pattern)

Display flow:
- RenderMarmotEncryptedMedia: parses imeta v=mip04-v2 tags, derives
  Mip04Cipher, registers in EncryptionKeyCache, renders via
  ZoomableContentView (same path as NIP-17 encrypted files)
- MarmotGroupList.noteToGroupIndex: reverse index for mapping notes
  back to their group ID (needed for exporter secret lookup)
- ChatMessageCompose NoteRow: routes MIP-04 events to the new renderer

https://claude.ai/code/session_01TckzZLpJdmE6p198DHBQ5i
2026-04-16 02:08:45 +00:00
Claude e168b9dde2 feat: replace MLS Groups FAB with Create Group and show marmot messages in notifications
Replace the "MLS Groups" FAB button on MessagesScreen with a "Create
Group" action that navigates directly to the group creation screen
(Route.CreateMarmotGroup) instead of the group list page. This aligns
with the plan to merge all marmot groups into the Messages screen's
Known and New Requests tabs.

Add marmot group messages to the notification screen so they appear
alongside regular DMs. This includes:
- Including marmot group messages in NotificationFeedFilter.feed()
- Accepting marmot group notes in acceptableEvent() via inGatherers check
- Converting marmot group notes to MessageSetCard in CardFeedContentState
- Routing marmot group notification clicks to the group chat screen

https://claude.ai/code/session_012wbykgUYHNVdHVNbDDnMUt
2026-04-16 01:55:44 +00:00
Vitor Pamplona 3356059d50 Removes warnings 2026-04-15 21:32:00 -04:00
Vitor Pamplona e4e7d4816a Fixes desktop compilation 2026-04-15 21:31:40 -04:00
Vitor Pamplona 51992dcf73 Merge pull request #2419 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-15 21:27:55 -04:00
Crowdin Bot af030aa1e2 New Crowdin translations by GitHub Action 2026-04-16 00:43:30 +00:00
Vitor Pamplona 85d9b60843 Merge pull request #2418 from vitorpamplona/claude/fix-group-metadata-persistence-kyTVP
Add persistent storage for Marmot KeyPackages and group messages
2026-04-15 20:42:01 -04:00
Vitor Pamplona 22dc89db70 Merge pull request #2417 from vitorpamplona/claude/review-nip34-compliance-WGPXe
Add NIP-34 Git pull requests, status events, and repository state
2026-04-15 20:41:37 -04:00
Claude 6a0f3982e8 feat(quartz): expand NIP-34 git collaboration coverage
Fills out Quartz's NIP-34 implementation so clients can build and parse
every spec-defined event kind, not just repository announcements and issues.

Kind 30617 (Repository Announcement):
- Multi-value web/clone accessors and builders
- relays, maintainers, hashtags, earliestUniqueCommit, isPersonalFork
- New tag classes: RelaysTag, MaintainersTag, EucTag
- New build() overload covering every optional tag

Kind 30618 (Repository State): new GitRepositoryStateEvent with
refs/heads, refs/tags, HEAD, and a RefTag/HeadTag pair.

Kind 1617 (Patch): rewrite builder to use the eventTemplate DSL with
required a/r/p tags, optional commit/parent-commit/commit-pgp-sig/
committer/root/root-revision markers, and a reply() helper that adds
NIP-10 marked e-tags pointing at a prior patch.

Kind 1618/1619 (Pull Request + Update): new GitPullRequestEvent and
GitPullRequestUpdateEvent with c/clone/branch-name/merge-base tags and
NIP-22 E/P parent references for updates.

Kinds 1630/1631/1632/1633 (Status Open/Applied/Closed/Draft): new
GitStatusEvent base + four subclasses sharing a GitStatusBuilders DSL.
Applied status carries merge-commit, applied-as-commits, and q tags for
the specific patch events that were merged.

Kind 10317 (User Grasp List): new UserGraspListEvent replaceable list of
g tags (websocket URLs in preference order) for discovering grasp
hosting servers.

NIP-51 Kind 10017 (Git Authors List): now uses a dedicated GitAuthorTag
class with optional petname (NIP-02 follow-list 4th element) instead of
the mute-list UserTag, so round-tripping follow lists no longer drops
petnames.

All new event kinds are registered in EventFactory. Spotless-clean and
compiles via :quartz:compileKotlinJvm.

https://claude.ai/code/session_018Fz3AeEdGkeFoeHBKuMwbH
2026-04-16 00:14:08 +00:00