mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-30 07:37:38 +00:00
The 33 descriptions (~16k chars, ~19k with paths) overran Codex's skill-list budget (2% of the context window, or 8,000 chars when unknown), and a review found most bodies stale: paths from before the commons/commonsUI moves, the deleted legacy desktop app, a callback signer API that no longer exists, and example APIs that were never in the tree. Kept, rewritten and checked against the code (every path, class and command): account-state (+feeds), amy-expert, android-expert, auth-signers, compose-expert (+API style, Navigation 3 back stacks), desktop-expert (+run/package), event-store-semantics (+NIP-50 search), find-missing- translations, kotlin-coroutines (+structured concurrency, flow modeling), kotlin-multiplatform (+Gradle, composable extraction), ngit-pr, nip85-trusted-assertions, nostr-expert (+NIP lookup, generated NIP catalog), relay-client. Dropped: textbook Compose/Kotlin technique skills, kotlin-expert, kotlin-types-value-class, gradle-expert, quartz-integration (external-facing; see the Quartz docs commit), find-non-lambda-logs (audit finished; the rule is now in AGENTS.md Kotlin Style), and the /nip, /desktop-run, /extract commands. The skill list is now ~4k chars. Also drops .claude/core-skills-plan.md (stale history) and repoints SearchableEvent's KDoc to event-store-semantics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.6 KiB
3.6 KiB
Rich Text Parsing
Event content (text with URLs, mentions, hashtags, media links, nostr references, invoices, markdown) is parsed headlessly into segments, then rendered by shared composables.
Parser (headless, commons)
All under commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/richtext/:
RichTextParser.kt:class RichTextParser,parseText(content, tags, callbackUri, authorPubKey)returns aRichTextViewerState.CachedRichTextParser.kt:object CachedRichTextParser, an LRU in front of the parser:parseText(...)(parse or hit),cachedText(...)(hit only, may return null),isMarkdown(content),trimToSize(maxItems). Always parse through this.RichTextParserSegments.kt:RichTextViewerState(urlSet,mediaForPager,mediaList,customEmoji,paragraphs,tags, allImmutable*),ParagraphState/ImageGalleryParagraph, and theSegmentsubclasses (ImageSegment,VideoSegment,LinkSegment,HashTagSegment,BechSegment,HashIndexUserSegment,InvoiceSegment,CashuSegment,RelayUrlSegment,MathSegment,RegularTextSegment, ...).Patterns.kt: the regex bank (URL, hashtag, mention, email, invoice, cashu, nostr URI). Add a case here instead of writing a regex at a call site.UrlParser.kt:UrlParser/Urls, URL extraction before classification.GalleryParser.kt: groups consecutive media into anImageGalleryParagraph.MediaContentModels.kt:MediaUrlImage,MediaUrlVideo,MediaUrlPdfand their encrypted siblingsEncryptedMediaUrlImage/EncryptedMediaUrlVideo.MediaLocalContent.kt:MediaLocalImage/MediaLocalVideofor not-yet-uploaded drafts.ExpandableTextCutOffCalculator.kt: where to fold long content for "Show more".
Renderer (commonsUI)
commonsUI/.../commons/ui/components/RichTextViewer.kt:RichTextViewer(content, ...), the entry point screens call. It checksCachedRichTextParser.isMarkdown, parses throughCachedRichTextParser.parseTextinsideremember(content, tags), and hands the state to the core below.commonsUI/.../commons/ui/richtext/RichTextViewer.kt: the shared core that takes a parsedRichTextViewerState: paragraphs, RTL, word flow, plain text, custom emoji, hashtags.commonsUI/.../commons/ui/richtext/RichTextSegmentRenderer.kt: the seam for segments whose look and action differ per platform (media, gallery, equation, nostr entity, quoted event, user mention, payment, ...), installed throughLocalRichTextSegmentRenderer;DefaultRichTextSegmentRenderer.ktis the default. See theREADME.mdin that folder.
To render a new segment type: add the Segment subclass and its detection in the parser, then
either draw it in the shared core (if it looks the same everywhere) or add a method to
RichTextSegmentRenderer.
NIP-92 imeta enrichment
imeta tags carry per-URL metadata (url, m, dim, blurhash, x, size). The parser
maps them onto MediaUrlImage / MediaUrlVideo so the renderer can reserve the aspect ratio
and show a blurhash before the image loads. The data comes from the event's tags, not the URL.
Gotchas
- Never parse per recomposition. Go through
CachedRichTextParser; it is keyed by content, tags, callback URI and author. - Encrypted media is a subclass.
EncryptedMediaUrlImageextendsMediaUrlImage(and the video likewise), so anis MediaUrlImagebranch also catches encrypted NIP-17 / Blossom media. Test for the encrypted class first wherever it needs decryption. - Changing
GalleryParsergrouping changes the layout of every note with several images.