account-state
Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow),…
Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging. Use when touching desktopApp, or to run/screenshot the shared UI headless (tools/desktop-run, Xvfb) to verify a UI change. NOT for writing shared
Claude Code
$ npx -y skills add vitorpamplona/amethyst --skill desktop-expert --agent claude-code
Codex
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a codex -y
Cursor
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a cursor -y
Gemini CLI
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a gemini-cli -y
GitHub Copilot
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a github-copilot -y
OpenCode
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a opencode -y
Windsurf
$ npx skills add vitorpamplona/amethyst --skill desktop-expert -a windsurf -y
name: desktop-expert description: Desktop JVM shim in desktopApp/: AmethystDesktop window, DesktopMenuBar, platform ports, OS keyring; :desktopApp:run, packaging. Use when touching desktopApp, or to run/screenshot the shared UI headless (tools/desktop-run, Xvfb) to verify a UI change. NOT for writing shared screens.
`desktopApp` is a **shim**: it opens a window and renders the same `AmethystApp` Android renders. Screens, the navigation shell (bottom bar / rail / drawer, chosen by `ScreenLayoutSpec`) and the deck are shared UI in `commonsUI` — see AGENTS.md "Navigation Shell" and "Feature Workflow", and `compose-expert`. Never add a desktop-only screen or a desktop-specific navigation layout. Background: `commons/plans/2026-09-27-one-ui-android-desktop.md`, `commons/plans/2026-10-09-legacy-desktop-retirement.md` (the legacy desktop app is gone).
| File | Role | |---|---| | `app/AmethystDesktop.kt` | `main()`: macOS AWT properties, data dir, `DesktopAppModules`, the `Window`, the CompositionLocals (`LocalAppPlatform`, `LocalNotePlatform`, …) around `AmethystApp`. `--publish-scheduled` runs headless (OS timer publishing scheduled posts). | | `app/DesktopAppModules.kt` | The desktop graph (Android's `AppModules` minus phone-only parts). | | `app/DesktopAppRoot.kt` | `AppRoot` impl; `jvmDestinations`. | | `app/DesktopAppPlatform.kt` | `AppPlatform` (commonsUI `ui/platform/`); unoverridden members keep the default, which hides the feature. | | `app/DesktopNotePlatform.kt`, `DesktopRichTextPlatform.kt` | `NotePlatform`, `RichTextPlatform` (commonsUI). | | `app/DesktopAccountViewModelHost.kt`, `DesktopLocalCacheHost.kt`, `DesktopAppServices.kt` | `AccountViewModelHost`, `LocalCacheHost`, `AppServices` (commons). | | `app/DesktopMenuBar.kt` | `DesktopNavigator`, the `Command` enum, `DesktopMenuBar`, `handleShortcut`. | | `app/Legacy*Import.kt` | One-time import of the retired legacy app's accounts, deck, feeds, privacy lock. | | `platform/PlatformInfo.kt` | `PlatformInfo.current` / `host`, `isMacOS`, `isWindows`, `isLinux`, `isGnome`, `isKde`. | | `platform/SavedWindowBounds.kt` | Window position/size persisted to `window-bounds.properties`. | | `platform/WaylandDisplay.kt`, `platform/AppWindow.kt` | Native Wayland: `WLToolkit` picked in `main()` on a Wayland session (`AMETHYST_X11=1` or the "Draw through XWayland" setting, `display.properties`, opts out); `AppWindow` is Compose's `Window` elsewhere and a Swing-drawn frame there. Open new top-level windows with `AppWindow`, never `Window`/`ComposeDialog`. | | `platform/DesktopUiScale.kt` | X11 at a fractional scale (`Xft.dpi` 120 = 125%, `AMETHYST_SCALE` by hand): AWT draws whole scales only, so `AppWindow` raises Compose's density by the rest. `LocalDensity` is then not AWT's scale: convert AWT window/event coordinates with `awtPixelScale(component)`, and size AWT windows in AWT units. | | `account/`, `tor/`, `network/`, `service/`, `ui/media/` | Account storage, Tor, HTTP, scheduled posts, video/audio players. | | `update/` | Self-update: `InstallChannel` (how this copy was installed: DMG, Homebrew, MSI, zip, AppImage, tarball, .deb/.rpm, Flatpak), `DesktopUpdateInstaller` + `UpdateHelperScripts` (stage, then swap after quit), `UpdateDownloader` (SHA-256 checked). The checker is commons `appupdate`; dev runs need `-Damethyst.update.devCheck=true`. |
A new shared feature that needs a desktop behaviour = a member on one of the ports above (default in the interface, override here), not a desktop branch in the shared screen.
Ctrl+key elsewhere (`isMac = PlatformInfo.host == Platform.MACOS`).
it inside the window); shortcuts go through `handleShortcut` from the window's `onPreviewKeyEvent`.
(`AUTO_REPEAT_WINDOW_MS`).
`PlatformInfo.current` honours an override (`-Damethyst.platform=GNOME` or `AMETHYST_PLATFORM`) to preview another OS's theming; `PlatformInfo.host` is the real OS — use it for anything that touches the actual system (menu bar, Cmd key, shell-outs).
Keys go to the OS keyring through `SecureKeyStorage` (`commons/src/jvmMain/.../commons/keystorage/SecureKeyStorage.kt`, `java-keyring`). Without one they go to a NIP-49 password file (`PasswordKeyFile`), and a login can opt out of keeping its key. The status, prompts and Settings › Key storage are shared (`KeyStorageController`, provided by `DesktopAppRoot.keyStorage`); see `commons/plans/2026-10-12-desktop-key-storage.md`.
The keyring breaks in release builds only, so test the packaged app: `Amethyst --keyring-self-test` (CI runs it through `keyring-self-test.sh` in the repo's top-level scripts folder). ProGuard must keep jkeychain's native library and dbus-java's ServiceLoader transport, and drop the signatures of jars it rewrites (hkdf): `desktopApp/compose-rules.pro`, `verifyJkeychainNativeSurvivesProguard`, `verifyDbusTransportSurvivesProguard`.
./gradlew :desktopApp:run # on the JDK Gradle runs on (21+; packages: 25) ./gradlew :desktopApp:run -Damethyst.dataDir=/tmp/amy-test # separate profile; -Damethyst.debug=true for DEBUG logs ./gradlew :desktopApp:packageDmg # macOS; packageMsi (Windows), packageDeb / packageRpm (Linux) xvfb-run ./gradlew :desktopApp:trainReleaseAotCache # AOT cache into the release image (Linux portable builds)
cache): BUILDING.md "Startup archi
Account, LocalCache, User/Note and feeds in commons/model + commons/feeds: per-user XState objects (.flow),…
amy CLI in cli/: adding an `amy <verb>` command, Main.kt dispatch, Output.emit/--json contract, exit codes 0/1/2/124, ~/.amy…
Android shim in `amethyst/`: MainActivity, AndroidManifest/intent filters, :napplet process, play/fdroid flavors, R8 +…
NostrSigner in Amethyst: NostrSignerInternal (local key), NostrSignerRemote (NIP-46 bunker), NostrSignerExternal (NIP-55 Android…
Shared Compose UI in `commonsUI/`: screens, components, the nav shell (`ScreenLayoutSpec`, `AppShellLayout`, Navigation 3 back…
Translation audits of `strings.xml` locales in `commonsUI/.../composeResources` and `amethyst/src/main/res`: missing or…
Decompile Android APK, XAPK, JAR, and AAR files using jadx or Fernflower/Vineflower. Reverse engineer Android apps, extract HTTP…