···11+# 0015 — Editor lede field + publish-time description excerpt fallback
22+33+- **Status:** Accepted
44+- **Date:** 2026-06-09
55+- **Scope:** The editor's new lede/excerpt field (`Studio.tsx` → `PublishPanel.tsx`), the
66+ publish flow's `description` (Decision 0005), and the reader's `og:description`
77+ (`[author]/[slug]/[rkey].astro`). Design: `docs/superpowers/specs/2026-06-09-editor-lede-excerpt-design.md`.
88+99+## Context
1010+1111+The companion Bluesky post (and its standard.site link card) showed an **empty description**
1212+whenever the writer didn't supply one: the editor had no description field at all, so
1313+`PublishPanel` never passed `description`, and the document record + `app.bsky.embed.external`
1414+were written with none / `""`. Observed live on
1515+`app.bsky.feed.post/3mnur47pyns27` (`"description": ""`).
1616+1717+Lexicon limits researched (2026-06-09): Bluesky's `app.bsky.embed.external.description` is an
1818+**unconstrained string** (no `maxLength`/`maxGraphemes`); Bluesky's composer doesn't expose it
1919+(it auto-fills from `og:description`). `site.standard.document.description` is optional with
2020+`maxLength: 30000` / `maxGraphemes: 3000` and guidance "a brief description or excerpt." Neither
2121+recommends a display length; the repo's own house convention is ~200 chars (the reader's
2222+`og:description` and RSS both derive a 200-char excerpt).
2323+2424+## Decision
2525+2626+1. **A visible lede field**, not an invisible auto-derivation. A controlled, auto-growing italic
2727+ `<textarea class="studio__lede">` sits under the title (echoing it: display face, borderless,
2828+ but quieter), value held as Studio's `excerpt` state and passed to `PublishPanel` as the
2929+ `description` prop. `maxLength={ 3000 }` guards the standard.site grapheme ceiling so a publish
3030+ is never rejected; a soft `> 200` hint warns about Bluesky-card truncation without blocking.
3131+2. **Publish-time fallback, written into the record.** The publisher is the single source of
3232+ truth: `description = input.description?.trim() || deriveExcerpt( textContent )`, used for
3333+ **both** the document record and the Bluesky embed. A blank lede therefore yields a real,
3434+ stored description (not just a rendered-meta fallback) — so the card, the publication
3535+ article-list blurb, and `og:description` are all populated.
3636+3. **One shared excerpt convention.** `deriveExcerpt` (`src/lib/publish/excerpt.ts`, pure,
3737+ dependency-free per Decision 0003) collapses whitespace, cuts on a word boundary at ≤ 200
3838+ chars, and appends an ellipsis. The reader's legacy fallback (`doc.description ||
3939+ deriveExcerpt(textContent)`) uses the same helper, replacing the old ad-hoc
4040+ `textContent.slice(0, 200)`, so the convention can't drift.
4141+4242+## Why a visible field over silent auto-derivation
4343+4444+The first instinct was to auto-generate `og:description` from body text. A visible lede is
4545+better: it gives the writer control over the share blurb (the reference tool, `pckt.blog`,
4646+auto-derives but offers no control), surfaces the behaviour (placeholder: "defaults to the
4747+opening of your post"), and still degrades to the auto-excerpt when left blank. Writing the
4848+derived value **into the record** (not deriving only at render) keeps the document, the Bluesky
4949+card, and the reader meta consistent, and means the standard.site card an external AppView
5050+renders is never empty.
5151+5252+## Notes / non-goals
5353+5454+- The *image* half of the same bare-card bug (a content image over the 1 MB `thumb` cap) is
5555+ separate — see Decision 0014 and `2026-06-09-bsky-post-thumb-design.md`.
5656+- Edits (`updateDocument`) refresh the stored description but don't re-post; the original
5757+ Bluesky post keeps its description (the panel already warns the preview may not refresh).
5858+- RSS keeps its own 200-char fallback for now; a later pass could re-point it at `deriveExcerpt`.
5959+- No lexicon change: `description` already exists on both records.