Commit Graph

12076 Commits

Author SHA1 Message Date
Vitor Pamplona 3aee60cbdf Merge pull request #2574 from vitorpamplona/claude/fix-lint-errors-LmosQ
fix(lint): observe locale via LocalConfiguration in composables
2026-04-25 13:42:44 -04:00
David Kaspar 8317933344 Merge pull request #2573 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 18:57:13 +02:00
Crowdin Bot b256fcc9d5 New Crowdin translations by GitHub Action 2026-04-25 16:47:58 +00:00
Claude b0508e1ec7 fix(lint): observe locale via LocalConfiguration in composables
Replace Locale.getDefault() with LocalConfiguration.current.locales[0]
inside the participant role rendering composables to satisfy the
NonObservableLocale lint check, ensuring the UI recomposes when the
system locale changes.

https://claude.ai/code/session_01AikVWBbPuWhqCDDFCwbeA2
2026-04-25 16:46:26 +00:00
Vitor Pamplona 41c1e131a0 Merge pull request #2572 from vitorpamplona/claude/compare-cli-interface-SEx9K
Dual-output contract: text by default, --json for machines
2026-04-25 12:46:08 -04:00
Claude 6ef0c372b1 docs(cli): make USAGE.md the README; move contract material to DEVELOPMENT
USAGE.md was the better README — entry-point users want examples and
quick start, not the public-API contract. Flip them and refresh the
amy-expert skill so it matches the post-refactor reality.

cli/README.md (was USAGE.md):
- Install, quick start, seven worked examples, full command reference,
  output modes, multi-account workflows, agent recipes, troubleshooting.
- Cross-refs point at DEVELOPMENT.md for the contract / architecture
  and ROADMAP.md for what's coming.

cli/DEVELOPMENT.md absorbs the old README's architecture sections:
- New "Public contract" section at the top — the stable promises
  (text-default + --json contract, stderr for humans, exit codes,
  ~/.amy/ as the world).
- "Local event store" deep-dive with the cache-helper API.
- "Relay routing" rules table.
- "Full on-disk layout" tree with annotations.

cli/ROADMAP.md, cli/USAGE.md:
- ROADMAP cross-refs collapsed (no more USAGE.md row).
- USAGE.md deleted — content lives in README now.

.claude/skills/amy-expert refreshed end-to-end:
- SKILL.md description + Rules 2 and 4 rewritten for the dual-output
  contract (text default, --json opt-in) and the ~/.amy/ layout.
- "Where things live" listing matches the current source tree
  (Output.kt, Aliases.kt, UseCommand.kt, secrets/, all the new
  command files).
- "Common mistakes" lists the new traps: don't read user.home
  directly, don't add a global flag that collides with subcommand
  --name, don't use Json.writeLine (it's gone).
- references/command-template.md uses Output.emit / Output.error
  (Json.writeLine / Json.error helpers no longer exist).
- references/output-conventions.md rewritten around the dual-mode
  contract — same JSON shape rules, but framed as "this is what
  --json emits" rather than "this is stdout."
2026-04-25 16:35:10 +00:00
Vitor Pamplona 58784d0124 Merge pull request #2571 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 12:04:56 -04:00
Crowdin Bot be8afe370b New Crowdin translations by GitHub Action 2026-04-25 16:02:27 +00:00
David Kaspar b0e73e84f4 Merge pull request #2568 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 18:02:20 +02:00
Vitor Pamplona 9a96a214c0 Merge pull request #2570 from davotoula/fix-zoom-dialog-squash
fix(zoom): keep aspect ratio during the avatar zoom-in animation
2026-04-25 12:00:57 -04:00
Claude 1b307e5955 fix(cli/tests): bind marmot harness relay to 127.0.0.2 (avoid localhost strip)
Quartz's RelayUrlNormalizer.isLocalHost strips literal 127.0.0.1 /
localhost / 192.168.* / .local / umbrel out of NIP-17 inbox
(kind:10050) and KeyPackage (kind:10051) relay-list events as a
privacy guard. The marmot harness was binding the loopback relay to
ws://127.0.0.1:8080, so when amy added that URL to its kind:10050 /
kind:10051 events the parser silently dropped it on read — leaving
the harness publishing to Amethyst's PUBLIC default relays
(nos.lol, nostr.mom, …) instead of the local one. Whitenoise (which
only listens on the local socket) never saw amy's KeyPackage,
breaking every Test 01–16 scenario at the first hop.

The DM harness already worked around this by using 127.0.0.2 (still
pure loopback, not on the strip list — see dm-interop-headless.sh:34).
This commit applies the same workaround to the marmot harness:

- Default RELAY_HOST=127.0.0.2 (overridable via env or --host).
- RELAY_URL is now derived from RELAY_HOST + RELAY_PORT.
- New --host flag for parity with the DM harness.

setup.sh's relay config already reads RELAY_HOST when binding
nostr-rs-relay (line 173, address = "${RELAY_HOST:-127.0.0.1}"), so
no other change is needed — the relay binds where amy is trying to
reach it.
2026-04-25 15:58:15 +00:00
davotoula 5861e3a7e5 fix(zoom): keep aspect ratio during the avatar zoom-in animation
The grow animation used independent scaleX/scaleY derived from the source
rect (square avatar) and the image rect (often landscape), so at the start
of the transition the full image was squashed into the avatar's square
footprint before lerping back to its real proportions.

Switches to a uniform scale based on max(src.width/img.width,
src.height/img.height) and centers the image on the tapped rect. The image
now keeps its aspect ratio throughout, covering the source rect in at
least one dimension at progress=0 and growing uniformly to fullscreen.

Also extends the entry guard to require non-zero source rect; without it
a not-yet-measured thumbnail would yield startScale=0 and collapse the
first frame to an invisible point before the animation expanded it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:52:12 +02:00
Claude fa48d7188f docs(cli): slim README, refresh DEVELOPMENT + ROADMAP for USAGE.md split
USAGE.md is now the single home for "how do I use amy" — install,
quick start, examples, command reference, troubleshooting. README
was carrying all of that PLUS the public-contract material; with
USAGE in place it can collapse to just the contract.

README.md (338 → 176 lines):
- Keep: 1-paragraph intro (three audiences), output contract, local
  event-store explainer, relay-routing rules, on-disk layout, cross-
  references to USAGE/DEVELOPMENT/ROADMAP.
- Drop: install, quick start, command reference table, global flags
  table, account-management verbs, troubleshooting (all in USAGE).

DEVELOPMENT.md:
- Architecture diagram updated to reflect new files (Aliases.kt,
  UseCommand.kt, ProfileCommands.kt, DmCommands.kt, NotesCommands /
  PostCommand / FeedCommand, MarmotResetCommand, StoreCommands,
  SecureFileIO, secrets/ subtree).
- "Keep three things in sync" pointers redirected from README's
  command table to USAGE.md.
- Testing table loses the duplicate "Interop with other clients" row
  (already covered by the harness row above).
- Cross-reference list at the top now mentions USAGE.md.

ROADMAP.md:
- Cross-reference list adds USAGE.md.
- Parity matrix marked  for items shipped on this branch:
  notes post + feed (PostCommand/FeedCommand), profile show+edit
  (ProfileCommands), DMs (already ).
- Reactions split into " in groups, 🆕 elsewhere" since
  marmot message react is shipped but outer-event reactions aren't.
- Order-of-operations entries marked  where relevant.
2026-04-25 15:48:32 +00:00
Claude c7d7d88a6c docs(cli): add USAGE.md — user-facing tour
Modeled on nostrcli.sh's docs page: quick start, seven worked examples
(post a note, send a DM, read a thread, view a profile, MLS group
round-trip, account switching, add a relay), categorised command
reference, output-modes section, multi-account workflow, agent/script
recipes, troubleshooting.

README.md and DEVELOPMENT.md are referenced from here; they stay
focused on the public contract and the extension rules.
2026-04-25 15:43:34 +00:00
Crowdin Bot 398e84a498 New Crowdin translations by GitHub Action 2026-04-25 15:43:03 +00:00
Vitor Pamplona c3ca03365f Merge pull request #2569 from davotoula/ci/add-android-lint
ci: add Android Lint as first step of the Android job
2026-04-25 11:41:38 -04:00
Claude e17ef42e54 fix(cli): rename global --name to --account to free --name for subcommands
The marmot harness surfaced the bug on first run: amy stripped
`marmot group create --name "Interop-02"` thinking the global
account selector ran into the group's display-name flag. Result:
amy resolved the "Interop-02" account, found no identity.json, and
errored — no group ever got created.

Renames the global account-selector flag to `--account`. The per-
subcommand `--name` flags (`marmot group create --name "Demo"`,
`profile edit --name "Alice"`, `marmot await group --name X`) are
untouched — they're free of the global parser now that it doesn't
claim the same name.

Sweep:
- `Main.kt`: GlobalFlag.NAME → ACCOUNT, long "--account".
- `Config.kt`: DataDir.resolve param renamed nameFlag → accountFlag;
  every error message points the user at --account.
- `UseCommand.kt`: error hint says `amy --account NAME init`.
- All test wrappers + direct $AMY_BIN calls under cli/tests/ swap
  the global `--name X` for `--account X` (subcommand --name kept
  exactly where it appeared).
- README + DEVELOPMENT updated.
2026-04-25 15:26:17 +00:00
Claude 99586fbe7e feat(cli): drop --data-dir; tests isolate via $HOME override
There is no installed base to protect, so the self-contained
`--data-dir P` escape hatch goes away entirely. amy now has exactly
one layout — the production multi-account one — and tests exercise
that same code path. Drops one global flag, one DataDir construction
mode, and the "tests use a different code path than users" footgun.

Layout (unchanged from the previous commits — just the only mode now):

    ~/.amy/
    ├── current                      # `amy use NAME` marker
    ├── shared/
    │   └── events-store/            # FsEventStore, one per machine
    ├── alice/
    │   ├── identity.json
    │   ├── state.json
    │   ├── aliases.json             # {"alice": "<own npub>"} after init
    │   └── marmot/
    └── bob/ ...

`DataDir.DEFAULT_ROOT` reads `$HOME` directly (falling back to
`user.home`) because JDK 21 resolves `user.home` via getpwuid and
ignores `$HOME` — which would have broken the standard CLI test
isolation pattern of `HOME=/tmp/foo amy …` (the same convention
git, gpg, npm follow).

Test sweep:

- `cli/tests/headless/helpers.sh`, `cli/tests/dm/setup.sh`,
  `cli/tests/cache/cache-headless.sh` wrappers all switch to
  `HOME=$STATE_DIR amy --name X …`.
- `ensure_identity_for` drops its `dir` parameter; the function and
  every harness call site go through `--name` only.
- `A_DIR`/`B_DIR`/`D_DIR` get repointed at `$STATE_DIR/.amy/X`. The
  one consumer (`cache-headless.sh`'s T6 `relays.json` check) still
  works since it's just a path probe.
- `cli/tests/dm/tests-dm.sh` ghost identities get their own
  short-lived `$HOME` so they don't pollute the main test root.

Cache-test T4 inverts: pre-shared-store, "B has not seen A → first
profile show is a relay miss, second is a cache hit" tested
per-account caching. With one shared events-store, B's first lookup
of A is already a cache hit because A wrote kind:0 there during
bootstrap. T4 now asserts that — drops the stale "second lookup hits
cache" half. T7 (no-identity maintenance verbs) gets a fresh fake
`$HOME` plus a throwaway `--name` so the empty store has no inherited
events.

Docs (README + DEVELOPMENT) rewritten to match — quick-start now uses
`amy --name alice create`, the on-disk-layout section shows the new
tree, and the global-flags table replaces `--data-dir` with `--name`
plus the new `amy use` verb.
2026-04-25 15:07:31 +00:00
davotoula fd1776fd07 ci: add Android Lint as first step of the Android job
Android Lint catches API-level, deprecation, accessibility, and resource
issues that spotless and unit tests miss. Run lint on the same variants
that get assembled (fdroidBenchmark, playBenchmark) before tests so a
lint failure surfaces quickly without consuming the full test budget.

Lint reports are uploaded as artifacts for inspection.
2026-04-25 17:04:50 +02:00
Vitor Pamplona 1876941d8f Merge pull request #2567 from vitorpamplona/claude/review-quartz-sqlite-store-xBzaq
Implement NIP-01 lexical id tiebreaker and NIP-09 author-only deletion
2026-04-25 10:41:09 -04:00
Claude 46bdd59ead feat(cli): account auto-pick + amy use to pin the active account
Resolution order in account mode (when --data-dir is not set):

  1. --name X if given.
  2. ~/.amy/current marker (set by `amy use X`).
  3. Sole subdirectory of ~/.amy/ other than shared/.
  4. Error — disambiguate with --name or `amy use`.

Single-account users skip the flag entirely (`amy whoami` Just Works
once one account exists). Multi-account users either pin one with
`amy use bob` (writes ~/.amy/current) or pass --name on every call.
The pinned account can be overridden by --name on a single command,
and cleared with `amy use --clear`.

`amy use` (no arg) prints the current pin plus the list of available
accounts. `amy use NAME` validates the name, requires the account dir
to already exist (else `no_account` with a creation hint), and
atomically writes the marker.

The auto-pick errors are deliberately self-explaining:
- 0 accounts → "no account at ~/.amy; create one with `amy --name X init`"
- 2+ accounts unpinned → "multiple accounts (alice, bob); pick one
  with --name or `amy use <name>`"
- stale current marker → "current pins 'ghost' but ~/.amy/ghost
  doesn't exist; rewrite with `amy use <name>` or pass --name"

`use` is dispatched before DataDir.resolve so it works even when the
auto-pick would fail — that's the whole point of having the verb.
The `name` field also lands in `whoami` output now (null in
--data-dir legacy mode).
2026-04-25 14:36:54 +00:00
Claude 076859ca2d feat(cli): introduce ~/.amy account-mode layout + --name flag
Default on-disk layout becomes:

    ~/.amy/
    ├── shared/
    │   └── events-store/        (lazy: created on first event)
    └── <account>/               (created by `amy --name X init`)
        ├── identity.json
        ├── state.json
        ├── aliases.json
        └── marmot/

Per-account state moves under `~/.amy/<account>/`; the events-store is
shared across accounts under `~/.amy/shared/`. The shared store is
safe to share for now because amy doesn't currently persist any
decrypted inner events to it (NIP-17 DMs unwrap-and-display in
DmCommands; MLS inner events go to the per-group .log under
<account>/marmot/groups/, not the event store). When that changes,
a follow-up will introduce a per-account private-events-store and a
composite reader.

A new `--name X` global flag selects (or creates) `~/.amy/X/`.
`--data-dir P` is preserved as a self-contained escape hatch — the
test harness and ad-hoc throwaway dirs use it, events-store stays
inside P. Pass exactly one of `--name` or `--data-dir`; passing both
or neither is bad_args (exit 2). Names must match
[a-zA-Z0-9_-]{1,64}; `shared` is reserved.

`amy init --name alice` writes a self-entry into
`<account>/aliases.json` ({"alice":"npub1…"}) so future commands and
the planned `amy alias add` / dm-recipient resolver can refer to the
account by name. The init result map gains a `name` key (null in
legacy mode).

Open question for a follow-up: when only one account exists in
~/.amy/, should `amy whoami` work without --name? Today it doesn't —
strict mode. Also pending: README rewrite, plus a sweep through
commands that print `data_dir` to also surface `name` where useful.
2026-04-25 14:32:19 +00:00
Claude 75bcd77914 docs(quartz/store): note the NIP-01 tiebreaker, NIP-09 created_at window, and author-check on deletion in both store READMEs
https://claude.ai/code/session_01X163Nr31vGkvAXoJ3JgMov
2026-04-25 14:30:59 +00:00
Claude 819322bbf9 fix(quartz/fs): port the same NIP correctness fixes from the sqlite store
Three of the bugs that the SQLite review surfaced also live in the
filesystem-backed event store. Bringing both stores back to parity:

- NIP-01 lexical-id tiebreaker (FsSlots, FsEventStore): when two
  replaceables / addressables share `createdAt`, the lexically smaller
  id wins. The previous `existing.createdAt >= incoming.createdAt`
  check rejected equal-timestamp inserts unconditionally — which
  blocks the legitimate winner whenever it arrived second.

- delete(Filter()) safe-by-default (FsEventStore): an empty filter
  used to enumerate every event and delete each one, so a stray
  `delete(Filter())` would wipe the entire on-disk store. Now both
  the single-filter and list-of-filters overloads short-circuit when
  every filter is empty, matching the SQLiteEventStore contract.

- NIP-09 author check on tombstone install (FsEventStore,
  FsTombstones): SQLite's `reject_deleted_events` trigger checks
  `event_tags.pubkey_hash = NEW.pubkey_owner_hash`, so a stranger's
  kind-5 with an `e`/`a` tag pointing at someone else's event must
  not block them from re-publishing. The FS store used to install
  the tombstone unconditionally and then read it back without an
  author check.
  - id tombstones still install (so they can fire when the deletion
    arrives before its target), but `idTombstoneOwnerPubKey` is now
    compared against the candidate event's owner pubkey at insert
    time. GiftWrap parity preserved via FsIndexer.ownerPubKey, which
    returns the recipient like the SQLite `pubkey_owner_hash`.
  - addr tombstones are now only installed when
    `addr.pubKeyHex == deletion.pubKey`, since the address itself
    carries the owner identity.

Tests:
- FsSlotsTest: replaces "equal timestamp replaceable is rejected"
  (which pinned the buggy behaviour) with two tests covering the
  lexical-id tiebreaker in both insertion orders.
- FsParityTest: new same-`createdAt` tiebreaker tests for both
  replaceable and addressable kinds, asserting FS and SQLite agree.
- FsDeletionTest:
  - inverts "deletion by non-author does not cascade but still
    installs id tombstone" — the legitimate owner must be able to
    re-insert after the stranger's deletion.
  - new test that a stranger's `a`-tag deletion does not block
    the legitimate addressable owner from publishing a new version.
- FsEventStoreTest: new `delete with empty filter is safe` test
  matching the SQLite-side contract added in batch A.

https://claude.ai/code/session_01X163Nr31vGkvAXoJ3JgMov
2026-04-25 14:24:28 +00:00
David Kaspar 03e4dca28b Merge pull request #2566 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 16:08:27 +02:00
Claude 9f83feff47 test(quartz/sqlite): cover transaction rollback, deletion permissions, vanish scope, FTS rotation, vacuum, SQL DSL
Batch B test-coverage gaps from the review:

- testTransactionRollsBackOnTriggerAbort: a multi-insert transaction
  whose middle statement is rejected by `reject_deleted_events` rolls
  back ALL inserts in the transaction, not just the failing one.

- testDeletionByThirdPartyDoesNothing: NIP-09 author check —
  another user's deletion event must not remove the original.

- testKind5CanBeDeletedByAnotherKind5OfSameAuthor: pins the current
  behavior that kind-5 events are not specially protected; a same-author
  follow-up deletion can remove a previous one, and re-insertion of the
  removed deletion is then blocked.

- testGiftWrapDeletionRequiresRecipient: NIP-59 GiftWraps key on the
  recipient (p-tag), not the (encrypted) inner author. Sender and
  unrelated third parties cannot delete; the recipient can.

- testVanishForDifferentRelayIsNoOp: a kind-62 vanish naming a
  different relay must be stored but must not delete events on this
  relay nor block new inserts (RightToVanishModule.shouldVanishFrom
  contract).

- testFtsCleanedUpAfterReplaceableRotation: FTS rows are cleaned via
  the AFTER DELETE trigger when an addressable is superseded — old
  content must no longer match search.

- testVacuumAndAnalyseSmoke: VACUUM and ANALYZE run on a populated DB
  without throwing and preserve existing rows.

- SqlSelectionBuilderTest: pins NotEquals(null) → IS NOT NULL,
  empty IN → "1 = 0", equalsOrIn singleton/multiple paths.

https://claude.ai/code/session_01X163Nr31vGkvAXoJ3JgMov
2026-04-25 13:56:28 +00:00
Crowdin Bot 1a93bb52f4 New Crowdin translations by GitHub Action 2026-04-25 13:52:34 +00:00
Vitor Pamplona 857d19f582 Merge pull request #2564 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 09:51:09 -04:00
Vitor Pamplona a8a267694d Merge pull request #2565 from davotoula/ci/split-android-job
Ci/split android job
2026-04-25 09:50:53 -04:00
Claude cd0b43afd9 fix(cli): expand embedded JsonNode in text-mode renderer
`profile show` puts the parsed kind:0 content under `metadata` as a
Jackson `JsonNode` (`ProfileCommands.kt:114`). The text renderer only
recursed into `Map`/`List`, so the JsonNode fell through to
`toString()` and printed as a single quoted-JSON line:

    metadata:       {"name":"Alice","picture":"…",…}

Convert any embedded JsonNode to plain Java types via
`mapper.convertValue` once at the top of `renderText`, so the same
generic walk yields:

    metadata:
      name:    Alice
      picture: …
      …

The walk handles nested cases (a hand-built Map containing a JsonNode
subtree, an ArrayNode inside a List, etc.). The `--json` shape is
untouched — Jackson's `writeValueAsString` already serialises JsonNode
natively.
2026-04-25 13:46:55 +00:00
David Kaspar 808a313e75 Merge branch 'vitorpamplona:main' into ci/split-android-job 2026-04-25 15:45:55 +02:00
Claude e9e994fff4 fix(quartz/sqlite): Batch A correctness/consistency fixes
- isExpired (#10): NIP-40 says an event is expired *once* `expiration`
  is reached, and the SQL trigger uses `<= unixepoch()`. The Kotlin
  pre-check used `<` (strict) so an event with `expiration == now`
  passed the Kotlin check then failed in the trigger. Both layers now
  use `<=`. Also applies to `isExpirationBefore` for consistency.

- transaction extension (#7): if the body throws *and* ROLLBACK also
  throws, we now attach the rollback failure as a suppressed exception
  instead of letting it mask the original cause. COMMIT is moved outside
  the catch so a commit failure doesn't trigger a second ROLLBACK on
  already-finalized transaction state.

- SeedModule.hasher (#8): the cache field is now a `kotlin.concurrent.
  atomics.AtomicReference` (matches the pattern used in BleChunkAssembler
  and BasicRelayClient) so the hasher publication is visible across
  threads. The race itself is benign — the seed is stable, so two
  concurrent computations produce identical hashers — but the prior
  plain `var` had no visibility guarantee.

- delete(Filter()) (#12): documents the intentional asymmetry — `query`
  on an empty filter returns everything, but `delete` on an empty
  filter is a no-op (safe-by-default). New test pins the contract.

Tests:
- testInsertingEventExpiringExactlyNow: events with `expiration == now`
  are rejected by both Kotlin and the trigger.
- testTransactionRollsBackOnException: a user transaction whose body
  throws leaves the DB unchanged and still accepts new writes.
- testDeleteWithEmptyFilterIsSafe: empty-filter delete is a no-op.

https://claude.ai/code/session_01X163Nr31vGkvAXoJ3JgMov
2026-04-25 13:31:16 +00:00
Claude ce3a82ab3d feat(cli): colour amy's text output and humanise scalar values
Defaults to ANSI colour when stdout is a TTY (off when piped, off under
NO_COLOR=…, force-on under CLICOLOR_FORCE=1). Layout improvements that
apply to every command without per-command code:

- Sibling keys at each indentation level pad to the widest, so colons
  line up YAML-style.
- Keys render bold; list dashes and "(none)"/"(empty)" markers render
  dim.
- Booleans render as `yes` / `no` (green / red).
- Integer values under `*_at` keys render as
  `2026-04-25 12:30:45Z (2m ago)` instead of a raw epoch second.
- Integer values under `*_bytes` keys (and `size`) render as
  `7.0 KiB` / `1.2 MiB` / etc.
- Errors render `error: <code>: <detail>` with the prefix bold-red and
  the code yellow.

The `--json` shape and exit codes are untouched; the smart formatting
only changes how scalars surface in the human-text mode.
2026-04-25 13:22:15 +00:00
davotoula adfdfa45cc test(quartz): also normalize CRLF on Jackson output
Jackson's pretty-printer can emit \r\n on Windows depending on the
configured DefaultIndenter, so the previous one-sided normalization
still mismatched. Normalize both sides of the assert to LF.
2026-04-25 15:16:37 +02:00
davotoula 680b1f0ef6 test(quartz): normalize CRLF in pretty-printer string asserts
Triple-quoted Kotlin strings inherit the source file's line endings,
so on Windows checkouts (CRLF) the expected JSON contains \r\n while
Jackson's pretty-printer always outputs \n. Normalize the expected
side with replace("\r\n", "\n") so the assertion is platform-neutral.

Surfaced by adding :quartz:jvmTest to the desktop CI matrix; previously
the bare 'test' lifecycle didn't resolve jvmTest in KMP modules so the
mismatch was never observed on Windows.
2026-04-25 15:01:33 +02:00
Claude 28b23b5e63 fix(quartz/sqlite): NIP compliance and migration safety in event store
- NIP-09 (#2): a-tag and replaceable deletes now respect
  `created_at <= deletion.created_at` so a stale deletion request
  cannot remove a newer addressable that legitimately replaced it.
  `+created_at` hint on the addressable path keeps the d_tag-selective
  index in use.

- NIP-01 (#1): replaceable / addressable triggers now apply the
  lexical-id tiebreaker — when two events share `created_at`, the
  one with the lexicographically smaller id wins, matching the spec.

- Schema (#5): FullTextSearchModule.versionFinder probes FTS support
  with a dummy table, but used to leave it behind. The first v1->v2
  upgrade then failed because re-running create() would hit
  "already exists". Now we drop the probe table immediately and
  defensively clean up any stragglers.

- Schema (#6): onCreate / onUpgrade and the matching `setUserVersion`
  are now wrapped in a single transaction so a partial migration
  cannot leave the DB with mismatched user_version and schema.

- SQL DSL (#4): Condition.NotEquals(null) now produces `IS NOT NULL`
  instead of `IS NULL`.

- Doc fix (#13): swapped vacuum/analyse comments now describe the
  right command.

Tests: same-`created_at` tiebreaker for replaceables and addressables,
NIP-09 created_at window for a-tag deletes, schema drop+recreate
idempotency (covers the FTS dummy-table regression).

https://claude.ai/code/session_01X163Nr31vGkvAXoJ3JgMov
2026-04-25 12:32:11 +00:00
Claude 03eb7be509 feat(cli): default amy stdout to human-readable text; --json opts in
amy's default stdout is now a YAML-ish render of the underlying result
map; the previous single-line JSON contract moves behind a global
`--json` flag. Errors mirror the same rule (`error: <code>: <detail>`
on stderr by default, JSON `{"error":...,"detail":...}` under --json).
Exit codes (0/1/2/124) and the --json shape itself are unchanged —
only the default presentation flips.

- Replaces Json.writeLine / Json.error with mode-aware
  Output.emit / Output.error. The same Jackson mapper is reused for
  on-disk JSON via Output.mapper.
- Adds `--json` to the global flag set in Main.kt; honoured even when
  argument parsing fails so error JSON keeps shape under --json.
- Updates the test harness wrappers (amy_a / amy_b / amy_d in
  cli/tests/{headless,dm,cache}/) and the few direct $AMY_BIN call
  sites whose stdout is consumed via $() / 2>&1 — they now pass
  --json so the existing jq pipelines keep working.
- Rewrites the README "Output contract" and DEVELOPMENT design
  principles to describe the new default, and clarifies that only
  --json is the public API; the text shape is allowed to drift.
2026-04-25 12:23:37 +00:00
davotoula 8a1b6c033f ci: use jvmTest for KMP modules in desktop leg
The bare 'test' task is ambiguous in KMP modules (candidates include
testAndroid and testAndroidHostTest). Switch quartz/commons/nestsClient
to :jvmTest so the desktop matrix unambiguously runs the JVM test
target on each OS. cli and desktopApp remain on :test (pure JVM).
2026-04-25 14:00:43 +02:00
Crowdin Bot 94df727c5b New Crowdin translations by GitHub Action 2026-04-25 11:28:36 +00:00
Vitor Pamplona fb478d6019 Merge pull request #2563 from davotoula/worktree-fix-avatar-squash
fix(avatar): center-crop profile picture thumbnails to prevent squashing
2026-04-25 07:27:08 -04:00
davotoula 06668c3c5e ci: include :nestsClient:test in desktop leg
nestsClient is a KMP module with both JVM and Android targets. Add it
to the desktop matrix's explicit test list so its JVM tests run on all
three desktop OSes alongside quartz/commons/cli/desktopApp.
2026-04-25 13:19:03 +02:00
davotoula 543ea03549 ci: split android into its own job for graph visibility
The merged test-and-build matrix hid Android behind an OS-keyed matrix
leg, so the workflow graph no longer showed a distinct Android node.
Restore visibility by splitting into two parallel jobs after lint:

- build-desktop (matrix: ubuntu/macos/windows): runs JVM tests for the
  KMP/JVM modules (quartz, commons, cli, desktopApp) and packages the
  native installer per OS.
- test-and-build-android (ubuntu): runs the full test sweep and
  assembleBenchmark, uploads APKs and reports.

Trade-off: Ubuntu spins up two parallel runners instead of one, but
wall time is unchanged and Android is once again a visible node.
2026-04-25 13:18:02 +02:00
David Kaspar 3720bd0cad Merge pull request #2562 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-25 13:11:04 +02:00
davotoula 550bf1dfef fix(avatar): center-crop profile picture thumbnails to prevent squashing
The profile picture thumbnail cache pre-resized every source to a square
256x256 via createScaledBitmap, stretching non-square avatars. The cache
hit path returned the pre-squashed bitmap with isSampled=true, so the
ContentScale.Crop at the composable could not undo it.

Fuses center-crop + scale into a single Bitmap.createBitmap call with a
scale matrix so the cached thumbnail matches the CircleShape +
ContentScale.Crop render contract with one allocation. try/finally
guarantees the source bitmap is recycled even if createBitmap throws.

Bumps the cache subdir to profile_thumbnails_v2 so existing squashed
thumbnails are abandoned and regenerated, and reclaims the orphaned v1
directory on first init.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:56:22 +02:00
Crowdin Bot a8823bdb85 New Crowdin translations by GitHub Action 2026-04-25 10:28:20 +00:00
davotoula 6e5a3224e5 minor sonar fixes 2026-04-25 12:23:20 +02:00
davotoula 1a77570591 update cz, se, de, pt 2026-04-25 11:49:31 +02:00
davotoula 1098447137 fix(marmot): handle ProposalStaged in GroupEventHandler.add
The GroupEventResult sealed class gained a ProposalStaged variant for
standalone Proposal events that get parked in the pending pool without
advancing the epoch. The when in DecryptAndIndexProcessor.add wasn't
exhaustive, breaking compileFdroidDebugKotlin and compilePlayDebugKotlin.

Logs the result at DEBUG since there's no UI work to do until a later
Commit references the proposal by hash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:47:02 +02:00
Vitor Pamplona 9e2ee3ace5 Merge pull request #2560 from vitorpamplona/claude/cli-event-store-database-ef2U2
A filesystem-backed event store
2026-04-25 01:09:37 -04:00
Claude dd3eb0de03 Merge remote-tracking branch 'origin/main' into claude/cli-event-store-database-ef2U2 2026-04-25 05:06:11 +00:00