Second audit pass over the remaining skills (amy-expert, auth-signers,
find-*, nostr-expert, quartz-integration, vendored technique skills),
verifying every concrete claim against the code:
- auth-signers: bunker login goes through NostrSignerRemote.fromBunkerUri
+ connect(), not the nonexistent RemoteSignerManager.connect(url)
- nostr-expert: NIP count 57 -> 80+; replace invented Nip44v2/Nip19
static APIs with the real Nip44 facade, ByteArray bech32 extensions,
entity create() helpers, and Nip19Parser.uriToRoute()?.entity
- nip-catalog: heading counts corrected to 87 standard + 23 experimental
packages with a ground-truth pointer
- quartz-integration: NIP-19 example rewritten for ParseReturn.entity;
Event Store is commonMain (all platforms), not Android-only, with the
real store.sqlite.EventStore import and suspend query<T> API
amy-expert, find-missing-translations, find-non-lambda-logs, the rest of
auth-signers, and the vendored technique skills audited clean.
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
Audit pass that verified every concrete claim in .claude/ against the
repository:
- account-state: Account.kt no longer exposes followListFlow-style
StateFlows; document the state-object pattern (kind3FollowList,
muteList, bookmarkState, ... each exposing .flow) and rewrite the
catalog reference from the real Account.kt
- feed-patterns: filter bases (FeedFilter, AdditiveFeedFilter,
ChangesFlowFilter, FeedContentState) moved to commons/ui/feeds;
ui/dal keeps AdditiveComplexFeedFilter/FilterByListParams plus
back-compat typealiases; fix recipe example signatures
- relay-client: add nip17Dm/, eoseManagers and subscriptions entries
to the layout tree
- gradle-expert: 4-module claim -> 10 modules; refresh compose/kotlin/
BOM versions; rewrite dependency graph with verified edges for cli,
geode, quic, nestsClient, quic-interop, benchmark
- desktop-expert: drop drifted Main.kt line numbers; sidebar is the
custom MainSidebar in DeckSidebar.kt, not a NavigationRail in
SinglePaneLayout.kt
- android-expert: compileSdk/targetSdk 36 -> 37, versionName via
generateVersionName()
- kotlin-expert: remove reference to nonexistent commit 258c4e011
- CLAUDE.md: add missing geode/benchmark/quic-interop modules
- desktop-run: packageRpm + correct binaries output path; extract.md:
drop duplicated find clause
- session-start.sh: /home/user/Amber fallback was a copy-paste from
another repo; fall back to CLAUDE_PROJECT_DIR
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
- CLAUDE.md: drop the 5-step skill-approval workflow (skills auto-trigger
and the approval loop blocked autonomous sessions), condense Verify-Don't-
Guess to the repo-specific tooling pointers, remove references to the
uncommitted /bugfix and /investigate skills, and replace the mandated
emoji survey matrix with one-line guidance
- android-expert / desktop-expert: add missing YAML frontmatter so the
skills carry trigger descriptions and can actually auto-invoke
- extract.md: fix stale shared-ui/ module name -> commons/
- delete skills/quartz-kmp.md breadcrumb (migration long complete)
- gate the Stop spotlessApply hook on modified Kotlin files via
hooks/stop-spotless.sh so Q&A-only turns skip the Gradle run
- condense core-skills-plan.md to a historical changelog
https://claude.ai/code/session_01EC7LdXjatFTh1CJSP4qKRn
Sweeps every typed kind: addressable kinds (30000..39999) must read
their d tag, plain replaceables (10000..19999, 0, 3) must ignore stray
ones — the invariant the kind-34235/34236 fix restores.
Extracts the priority sort into rankPriorityFirst() and covers: priority
users move to the top, stable order within both groups, no injection of
non-matching priority keys, and untouched list when priority is empty.
The MLS/Marmot inner message kind was missing from isChatEvent, so a
chat message quoted inside an MLS chatroom message still rendered as the
default NoteCompose card instead of the chat reply design.
https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
Moves the Marmot composer's inline state (message TextFieldState,
reply state, upload state, @-mention suggestion wiring, send) into a
ViewModel mirroring ChatNewMessageViewModel / ChannelNewMessageViewModel /
NestNewMessageViewModel, so all four chat types share the same
init/load structure. No behavior change.
Renders a small chip on suggestion rows whose pubkey is in the
suggestion state's priorityPubkeys set, unless the caller supplies
its own trailingContent. Priority keys only reorder and label the
users that already matched the search — they never inject results.
The MLS/Marmot inner message kind was missing from isChatEvent, so a
chat message quoted inside an MLS chatroom message still rendered as the
default NoteCompose card instead of the chat reply design.
https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
- Move NewMessageTagger from the Marmot composer into
AccountViewModel.sendMarmotGroupMessage so every send path gets
mention rewriting + p-tagging, not just the chat composer.
- Pass the parent's MarmotGroupChatroom into the composer instead of
re-fetching it from the group list.
- Bound the public-channel participant scan with a one-month cutoff
(matches the recency-cutoff convention in ChannelObservers).
- Simplify the nests participant-set construction.
In MLS kind-9 chats Note.replyTo keeps both replies and quotes (e and q
tags), so a message quoting another chat message rendered it twice: once
in the reply row and once at its inline nostr: mention. Render the reply
row only when the target is not cited in the content; the inline quote
renderer already draws it in place.
https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
Adds an optional priorityPubkeys supplier to UserSuggestionState that
stable-sorts search results so the current conversation's participants
appear before network-wide matches (ranking only, never filters).
Wired per chat context:
- MLS/Marmot groups: live MLS member list
- NIP-17 DMs/groups: the room's users
- Public chats (NIP-28): authors who have posted in the channel
- Nests audio rooms: MeetingSpaceEvent participants + host
https://claude.ai/code/session_013NWdjCSegsf2FYSPPANX3n
Brings the MLS/Marmot message composer to parity with NIP-17 DMs:
typing @ shows the shared user-suggestion dropdown (local cache +
NIP-05 resolution), selecting a user inserts @npub…, and on send
NewMessageTagger rewrites mentions into nostr: URIs and collects
the referenced users as p-tags on the inner kind:9 rumor. Mentions
stay inside the MLS ciphertext; the outer kind:445 is unchanged.
Also applies MentionPreservingInputTransformation and
UrlUserTagOutputTransformation to the field so mentions render
highlighted while composing, matching the DM editor.
https://claude.ai/code/session_013NWdjCSegsf2FYSPPANX3n
The trailing characters after a bech mention are part of the surrounding
text, so InlineQuoteRenderer implementations no longer receive extraChars;
DisplayFullNote draws them after delegating the note itself.
https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
Inline nostr: quotes used to always render through NoteCompose's quoted-note
card, even when the quoted event was itself a chat message shown inside a
chat bubble. Introduce an InlineQuoteRenderer strategy behind a
CompositionLocal: both rich-text paths (plain and markdown) funnel through
DisplayFullNote, which now reads LocalInlineQuoteRenderer, defaulting to the
existing NoteCompose card.
ChatroomMessageCompose provides a chat-aware renderer for everything inside a
bubble, so quoted chat events (NIP-17/NIP-04 DMs, NIP-28 channel messages,
NIP-53 live chat, ephemeral chat) reuse ChatroomMessageCompose with
innerQuote = true — the same design as the reply row — including
scroll-to-message on tap for quotes within the same room. Non-chat events and
not-yet-loaded quotes keep the default card.
https://claude.ai/code/session_01DSQW7kku5cGEL36icXg6BC
fixMissingSpaces runs on the main thread once per rendered note. The scan
introduced in the previous commit re-tested every detected URL at every
character position (and allocated an iterator per position via firstOrNull),
i.e. O(N*U*L) for a note with U URLs — noticeable on large notes that carry
many links.
Bucket the URLs by their first character once up front and only attempt a
match at positions whose character can actually start a URL; every other
character now costs a single map lookup, keeping the pass linear in the text
length for typical content. Buckets stay longest-first so prefix URLs still
don't shadow longer ones, so the output is identical (verified against the
commons richtext JVM corpus).
RichTextParser.fixMissingSpaces used a Regex of the form
`([^ \n])?(urls)([^ \n])?` to insert spaces around URLs glued to
neighbouring text. Kotlin/Native's regex engine fails to backtrack the
optional `([^ \n])?` capture groups to zero width, so on iOS every URL was
corrupted (e.g. "https://x" became "h https://x"). That broke the
downstream segmenter, which is why :commons:iosSimulatorArm64Test reported
19 failures across the RichText/Gallery/Pdf/F4a parsers once the test binary
finally linked.
Replace the regex with a direct left-to-right scan that inserts a single
space wherever a detected URL touches a non-space/non-newline neighbour. The
scan is engine-independent, so it behaves identically on JVM and Native.
Verified equivalent to the old behaviour across the full commons richtext
JVM corpus, and the new FixMissingSpacesTest pins the cases on every target
(including iosSimulatorArm64).
The :commons:linkDebugTestIosSimulatorArm64 CI step fails under Xcode 16.4
with 'Undefined symbols: _OBJC_CLASS_$_UIViewLayoutRegion'. The symbol is
referenced by the prebuilt Kotlin/Native cache of Compose Multiplatform's
org.jetbrains.compose.ui:ui-uikit (CMPLayoutRegion), which was built against
a newer simulator SDK (18.5) than the test binary is linked for (14.0).
Disable the native compiler cache for the iOS test binaries so ui-uikit
recompiles against the active SDK, where UIViewLayoutRegion resolves. The
DisableCacheInKotlinVersion guard re-surfaces the workaround once we move
past Kotlin 2.3.21 so it can be removed when the cache is fixed upstream.
Backs out the reactive list-refresh plumbing added in the prior commit:
removes the `changes` SharedFlow from the shared `ChatroomList` (restoring
it to its original form) and reverts Desktop `ChatroomListState` to its
original 2s poll. Room assembly is expected to move to a LocalCache.observe
approach on both platforms later, which would supersede this.
Keeps the independent Desktop list improvements (per-room unread tracking
and the mute/acceptable filter), which don't depend on the flow.
https://claude.ai/code/session_01VEukNczAYxNLBjLnqVEoZd
The test-quartz-ios job only compiled :commons production code for iOS
(compileKotlinIos*), never the commonTest source set, so a test using a
JVM-only API compiled fine on JVM/Android and shipped green while silently
breaking compileTestKotlinIosSimulatorArm64.
Mirror the quartz step: run :commons:iosSimulatorArm64Test (compiles +
runs the shared commonTest suite on the simulator) and
:commons:compileTestKotlinIosArm64 (device-only compile drift). This keeps
commonTest KMP-clean going forward.
Makes the Desktop DM client a first-class group participant and tightens
the shared/Desktop DM paths so they match Android behavior.
- commons ChatNewMessageState: actually attach the composed NIP-14 subject
to sent messages (the field was previously collected but dropped).
- commons ChatroomList: emit a `changes` SharedFlow on add/remove so list
UIs can refresh reactively; dedupe the User overloads onto the room ones.
- Desktop NewDmDialog: multi-recipient selection (chips + confirm button) so
a Desktop user can start a group, not only a 1:1.
- Desktop ChatPane/ChatroomHeader: show a group's NIP-14 subject in the
header and add a rename dialog that broadcasts a subject change to all
members.
- Desktop Main.kt DM ingest: route any ChatroomKeyable inner event into the
room (covers kind 14/15 and future variants) and store self-authored
NIP-37 drafts instead of dropping them.
- Desktop ChatroomListState: refresh reactively off ChatroomList.changes
(with a slower safety poll), track real per-room unread via a last-seen
mark, and hide rooms whose latest message isn't acceptable (mute/filter).
https://claude.ai/code/session_01VEukNczAYxNLBjLnqVEoZd
A kind:1 note carrying a `q` tag is a quote-repost of the quoted note,
but Amethyst's reaction-row repost counter reads `Note.boosts`, which
only collected kind:6/kind:16 reposts. Quote-reposts were treated purely
as inline citations (stripped by `tagsWithoutCitations()`), so they never
appeared in the quoted note's repost count.
LocalCache now adds a `q`-tagged note as a boost of each quoted note when
consuming text notes/comments, and detaches it on deletion. The quoted
note is deliberately kept out of `replyTo` so the quote still renders as a
root post in the home feed (`Note.isNewThread`). The same wiring is added
to DesktopLocalCache for parity, with tests pinning the behavior using the
exact event reported.