A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

Decision 0015: editor lede field + description excerpt fallback

+59
+59
docs/decisions/0015-description-excerpt-fallback.md
··· 1 + # 0015 — Editor lede field + publish-time description excerpt fallback 2 + 3 + - **Status:** Accepted 4 + - **Date:** 2026-06-09 5 + - **Scope:** The editor's new lede/excerpt field (`Studio.tsx` → `PublishPanel.tsx`), the 6 + publish flow's `description` (Decision 0005), and the reader's `og:description` 7 + (`[author]/[slug]/[rkey].astro`). Design: `docs/superpowers/specs/2026-06-09-editor-lede-excerpt-design.md`. 8 + 9 + ## Context 10 + 11 + The companion Bluesky post (and its standard.site link card) showed an **empty description** 12 + whenever the writer didn't supply one: the editor had no description field at all, so 13 + `PublishPanel` never passed `description`, and the document record + `app.bsky.embed.external` 14 + were written with none / `""`. Observed live on 15 + `app.bsky.feed.post/3mnur47pyns27` (`"description": ""`). 16 + 17 + Lexicon limits researched (2026-06-09): Bluesky's `app.bsky.embed.external.description` is an 18 + **unconstrained string** (no `maxLength`/`maxGraphemes`); Bluesky's composer doesn't expose it 19 + (it auto-fills from `og:description`). `site.standard.document.description` is optional with 20 + `maxLength: 30000` / `maxGraphemes: 3000` and guidance "a brief description or excerpt." Neither 21 + recommends a display length; the repo's own house convention is ~200 chars (the reader's 22 + `og:description` and RSS both derive a 200-char excerpt). 23 + 24 + ## Decision 25 + 26 + 1. **A visible lede field**, not an invisible auto-derivation. A controlled, auto-growing italic 27 + `<textarea class="studio__lede">` sits under the title (echoing it: display face, borderless, 28 + but quieter), value held as Studio's `excerpt` state and passed to `PublishPanel` as the 29 + `description` prop. `maxLength={ 3000 }` guards the standard.site grapheme ceiling so a publish 30 + is never rejected; a soft `> 200` hint warns about Bluesky-card truncation without blocking. 31 + 2. **Publish-time fallback, written into the record.** The publisher is the single source of 32 + truth: `description = input.description?.trim() || deriveExcerpt( textContent )`, used for 33 + **both** the document record and the Bluesky embed. A blank lede therefore yields a real, 34 + stored description (not just a rendered-meta fallback) — so the card, the publication 35 + article-list blurb, and `og:description` are all populated. 36 + 3. **One shared excerpt convention.** `deriveExcerpt` (`src/lib/publish/excerpt.ts`, pure, 37 + dependency-free per Decision 0003) collapses whitespace, cuts on a word boundary at ≤ 200 38 + chars, and appends an ellipsis. The reader's legacy fallback (`doc.description || 39 + deriveExcerpt(textContent)`) uses the same helper, replacing the old ad-hoc 40 + `textContent.slice(0, 200)`, so the convention can't drift. 41 + 42 + ## Why a visible field over silent auto-derivation 43 + 44 + The first instinct was to auto-generate `og:description` from body text. A visible lede is 45 + better: it gives the writer control over the share blurb (the reference tool, `pckt.blog`, 46 + auto-derives but offers no control), surfaces the behaviour (placeholder: "defaults to the 47 + opening of your post"), and still degrades to the auto-excerpt when left blank. Writing the 48 + derived value **into the record** (not deriving only at render) keeps the document, the Bluesky 49 + card, and the reader meta consistent, and means the standard.site card an external AppView 50 + renders is never empty. 51 + 52 + ## Notes / non-goals 53 + 54 + - The *image* half of the same bare-card bug (a content image over the 1 MB `thumb` cap) is 55 + separate — see Decision 0014 and `2026-06-09-bsky-post-thumb-design.md`. 56 + - Edits (`updateDocument`) refresh the stored description but don't re-post; the original 57 + Bluesky post keeps its description (the panel already warns the preview may not refresh). 58 + - RSS keeps its own 200-char fallback for now; a later pass could re-point it at `deriveExcerpt`. 59 + - No lexicon change: `description` already exists on both records.