Mentions: show typeahead suggestions as the writer types
The @mention autocompleter reused lookupActor, an exact
app.bsky.actor.getProfile resolve gated by isValidHandleOrDid. The
suggestion menu therefore only appeared once a writer typed a complete,
valid handle; partial input ('ria') failed validation and surfaced
nothing, making the feature effectively undiscoverable.
Add searchActors, which queries the public, unauthenticated
app.bsky.actor.searchActorsTypeahead and returns a short list of
matches, and point the completer's options() at it. Matches now appear
as the writer types, mirroring the Bluesky composer.
searchActors keeps lookupActor's safety posture (fixed host constant, q
URL-encoded as the only user input, never throws -- resolves to [] on
any failure) but is deliberately not gated by isValidHandleOrDid, since
partial input is the whole point. The inserted skypress/mention anchor
and pick-time DID resolution are unchanged.
Reader: seal the article render pipeline behind one render-article module
Turning a stored block tree into safe HTML takes three calls in one
load-bearing order — resolveBlobImageUrls → renderBlocks →
sanitizeArticleHtml — plus the plain-text fallback (textContent ||
blocksToText). That ordering is the AGENTS.md #6b invariant (sanitise
is the last step before injection), but no module owned it: the
article page and the feed builder each assembled it by hand, and a
third read surface (AMP, email, embeds) would have had to re-learn
the order — getting it wrong ships unsanitised PDS HTML.
Add src/lib/reader/render-article.ts, one deep interface:
renderArticle( doc, { pdsUrl, did } ) → { html, text }
The pipeline-order invariant gains locality — it lives once, behind
the interface, behaviourally tested in render-article.test.ts (a
<script> in block content never survives; blob image URLs are rebuilt
against the author's current PDS before render). Future read surfaces
get sanitisation for free by construction instead of by convention.
The module accepts the document-value slice rather than raw blocks so
the text fallback concentrates there too; SkyDocumentValue and the
feed's FeedDocumentValue both satisfy it structurally. Like render.ts
underneath it is dependency-free (no @wordpress/*, Decision 0003) and
render fidelity stays locked by the untouched render.test.ts.
The article page and buildPublicationFeedXml become callers; the feed
stays a pure transform with its behavioural tests unchanged. AGENTS.md
rule 6(b) now points at the module, and Decision 0017 records the
seam (closing the deepening Decision 0016 deferred).
Smoke-tested against trunk on the dev server: the article page, the
RSS body, and the 404 paths render identically (modulo dev-only
source-loc attributes and the random astro-island uid).
Mentions: show typeahead suggestions as the writer types
The @mention autocompleter reused lookupActor, an exact
app.bsky.actor.getProfile resolve gated by isValidHandleOrDid. The
suggestion menu therefore only appeared once a writer typed a complete,
valid handle; partial input ('ria') failed validation and surfaced
nothing, making the feature effectively undiscoverable.
Add searchActors, which queries the public, unauthenticated
app.bsky.actor.searchActorsTypeahead and returns a short list of
matches, and point the completer's options() at it. Matches now appear
as the writer types, mirroring the Bluesky composer.
searchActors keeps lookupActor's safety posture (fixed host constant, q
URL-encoded as the only user input, never throws -- resolves to [] on
any failure) but is deliberately not gated by isValidHandleOrDid, since
partial input is the whole point. The inserted skypress/mention anchor
and pick-time DID resolution are unchanged.