diff --git a/docs/NEXT.md b/docs/NEXT.md index 47865b7..ac0cece 100644 --- a/docs/NEXT.md +++ b/docs/NEXT.md @@ -2,7 +2,7 @@ The flight plan. Each item carries enough context to start cold; update this file whenever an item lands (move it to "Done") or a new one is queued. Decisions made while working an item still go through `decisions/` as usual. -_Last updated: 2026-08-27 (**PD-11 recorded: the site-metrics offering** — free real-7d / paid-30d on Analytics Engine, design fully settled in `research/2026-08-26-analytics-offering.md`, queued as item 2 below. Also: **PRs #27, #28 and #31 are all merged and deployed.** mooring.page now serves the demo-first landing page (PD-10) — verified live: the apex carries the Open Graph card, `/s/jzweifel.dev` renders with the claim banner and `noindex`, and the domain lock, sign-off band and republished `signOff` lexicon from the earlier PRs are all in place. **PR #33 is merged and deployed too** — the postmark cut-off fix and the landing footer's attribution row (both below), smoke-tested live. **PR #35 is merged and deployed** — the admin and login pages wear the site letterhead (details below). **PR #37 is merged and deployed too** — it fixes the white frame #35 shipped with (the `body { margin: 0 }` reset lived only in SiteLayout's `:global` styles, so routes that never bundle SiteLayout kept the default body margin; `letterhead.css` now resets body margin and paints `html` with background + `color-scheme` via `:has(.letterhead)`, mirroring SiteLayout's own root treatment) — verified live: the served letterhead stylesheet on mooring.page/login carries the reset in both schemes. **PR #39 is merged and deployed too** — the theme's contrast guards for user color overrides (details below); verified live in both schemes. **PR #41 is merged and deployed too** — Bluesky post embeds render instead of the cue (details below); verified live. **PR #43 is open and not yet merged** — trimmed posts/writing sections now close with a cue carrying the count and a link to where the rest lives, and the career log stays whole by decision (details below); this closes the *volume behavior* sub-item of the theme tail. **#43 is in flight; the rest of the queue below is current.** The queue below is otherwise current. Critique trends, two separate targets: the rendered **theme** ran 25 → 31 → 29 → 32, and the **landing page** has one run at 21/40 — snapshots in `.impeccable/critique/`)._ +_Last updated: 2026-08-27 (**PD-11 recorded: the site-metrics offering** — free real-7d / paid-30d on Analytics Engine, design fully settled in `research/2026-08-26-analytics-offering.md`, queued as item 2 below. Also: **PRs #27, #28 and #31 are all merged and deployed.** mooring.page now serves the demo-first landing page (PD-10) — verified live: the apex carries the Open Graph card, `/s/jzweifel.dev` renders with the claim banner and `noindex`, and the domain lock, sign-off band and republished `signOff` lexicon from the earlier PRs are all in place. **PR #33 is merged and deployed too** — the postmark cut-off fix and the landing footer's attribution row (both below), smoke-tested live. **PR #35 is merged and deployed** — the admin and login pages wear the site letterhead (details below). **PR #37 is merged and deployed too** — it fixes the white frame #35 shipped with (the `body { margin: 0 }` reset lived only in SiteLayout's `:global` styles, so routes that never bundle SiteLayout kept the default body margin; `letterhead.css` now resets body margin and paints `html` with background + `color-scheme` via `:has(.letterhead)`, mirroring SiteLayout's own root treatment) — verified live: the served letterhead stylesheet on mooring.page/login carries the reset in both schemes. **PR #39 is merged and deployed too** — the theme's contrast guards for user color overrides (details below); verified live in both schemes. **PR #41 is merged and deployed too** — Bluesky post embeds render instead of the cue (details below); verified live. **PR #43 is merged and deployed too** — trimmed posts/writing sections close with a cue carrying the count and a link to where the rest lives, and the career log stays whole by decision (details below); verified live on three sites. This closes the *volume behavior* sub-item of the theme tail. **Nothing is in flight; the queue below is current.** The queue below is otherwise current. Critique trends, two separate targets: the rendered **theme** ran 25 → 31 → 29 → 32, and the **landing page** has one run at 21/40 — snapshots in `.impeccable/critique/`)._ ## Where things stand @@ -16,7 +16,7 @@ Done so far: OAuth login (loopback dev client; hosted-client path ready pending - **Theme UX polish, remaining tail** (all mechanical findings from four critique rounds are fixed and deployed; the contrast guards landed 2026-08-27, see Done; what remains is design-decision territory — mock on a canvas before coding, per this push's working pattern): 1. *Record-level career curation fields* (decided 2026-08-24, staged): hide/order/feature for positions, education, skills — e.g. de-emphasizing a high-school entry or dropping generic imported skills ("Software Development", "Windows 7"); becomes `careerSection` fields once the admin grows a section editor. Framing from the reviews: "honest by default, curatable by choice." - 2. *Volume behavior* — **resolved 2026-08-27, PR #43 open** (see Done). Posts and writing now close a trimmed list with a cue that carries the count and links to where the whole of it lives; the career log stays whole by decision. Nothing left here unless a heavy poster's site argues for a real archive route. + 2. *Volume behavior* — **resolved 2026-08-27, PR #43 merged and deployed** (see Done). Posts and writing now close a trimmed list with a cue that carries the count and links to where the whole of it lives; the career log stays whole by decision. Nothing left here unless a heavy poster's site argues for a real archive route. 3. *Residual*: no RSS; postcard hero still unexercised by a real record (flip `heroSection.style` on a site record to demo); mono-metadata density (the detector's standing flat-type finding, 10.9–11.5px cluster — deliberate system, revisit only if it keeps bothering reviewers). - **Thin deletion-honoring cache** over the direct PDS reads (ADR 0010 §4) — deliberately reordered below hosting on 2026-08-07: rendering reads live per request, which is correct until there is traffic to cache. The demo-first landing page (PD-10) raises the pressure: the apex now invites uncached live reads for arbitrary handles, so abuse or traffic on the lookup form is the trigger. Revisit once sites are actually served; when it comes up, start from Jetstream v2's snapshot+tail replay (`research/2026-08-14-bluesky-protocol-services.md`) rather than a relay consumer. - **Landing-page critique follow-ups, design-decision tier** (queued 2026-08-25; snapshot `.impeccable/critique/2026-08-26T01-52-10Z__apps-web-src-routes-page-svelte.md`, scored 21/40 — the three P1s and the mechanical P2s are fixed; what remains needs product calls): gloss or drop "standard.site"/"sifa" jargon for the non-technical launch audience; a pricing signal ("free subdomain" reads as an unpriced paywall); accept a pasted DID in the lookup (currently lowercased and rejected as a typo); distinguish resolver outages from typos in the error copy; and the big swing — show a real rendered site on the landing page instead of describing one ("you've built a rendering engine and put a text ad in front of it"). @@ -36,7 +36,7 @@ The whole design is settled and recorded — `research/2026-08-26-analytics-offe ## Done -- 2026-08-27 — **A section that stops short says so, and says where the rest is** (PR #43, **open**). Posts and writing rendered a fixed head of their collections and simply stopped: a reader with 342 posts saw five, with nothing on the page saying so and nowhere to go for the rest, while the career log rendered every record — three inconsistent volume behaviours on one page. Jacob's calls, all three the recommended option: **a stamped cue that links out** rather than an in-place disclosure or an archive route (quote hydration deliberately runs only over the *shown* posts — each quote costs a DID-document resolution plus a post read — so revealing forty more would either pay for forty more of those or render the hidden ones bare); **the career log stays whole** (a résumé's point is showing the log, its lines are compact, and a truncated work history reads as concealment rather than curation — so `positions`/`education` are untouched, and the existing `SKILL_CAP` disclosure stands); and **the default post count moves 5 → 8**, which an honest cue makes affordable. A trimmed list now closes with one line in the theme's system voice — mono, accent, above a dotted rule that replaces the list's own last border: `8 of 342 · the rest on Bluesky ↗`. **The count turned out to be free**: both adapters already page the entire collection (`listAllRecords`, 10 × 100) before slicing, so they now return it — `fetchBlueskyPosts` → `{ posts, total }`, `fetchDocuments` → `{ documents, total, publication? }` (replies count towards neither). **The line only renders when there is somewhere to send the reader** — a count with no destination is a tease, not an affordance. Posts always have one, the owner's Bluesky profile by DID, matching the two links `SiteLayout` already builds. Writing has one when every listed document belongs to a single publication that publishes an address; documents spread across publications, or `pub.leaflet.*` legacy records which name none, get no line, and the label is the publication's name falling back to its host. Resolving that publication costs a read, so it is only attempted once a listing is known to be short, the two things wanting publication records now share one read instead of two, and a failed read costs the cue rather than the listing. No lexicon change, no record migration, no new route. 7 new tests (224 in `web`); verified in a browser through a temporary preview route covering posts-trimmed, posts-whole, writing-with-a-named-publication and writing-falling-back-to-its-host in both schemes, deleted before commit — accent-on-paper measured 6.58:1 light / 5.46:1 dark, 36px tall, no horizontal overflow at 375px. Checked against live PDS data: `jzweifel.dev` has five non-reply posts, so it is trimmed by neither the old limit nor the new one and renders unchanged. **One gap:** no account in hand exercises the cue against live data — it is covered by the tests and the dev preview, not by a deployed site; look the first time a heavier poster's site is rendered. +- 2026-08-27 — **A section that stops short says so, and says where the rest is** (PR #43, **open**). Posts and writing rendered a fixed head of their collections and simply stopped: a reader with 342 posts saw five, with nothing on the page saying so and nowhere to go for the rest, while the career log rendered every record — three inconsistent volume behaviours on one page. Jacob's calls, all three the recommended option: **a stamped cue that links out** rather than an in-place disclosure or an archive route (quote hydration deliberately runs only over the *shown* posts — each quote costs a DID-document resolution plus a post read — so revealing forty more would either pay for forty more of those or render the hidden ones bare); **the career log stays whole** (a résumé's point is showing the log, its lines are compact, and a truncated work history reads as concealment rather than curation — so `positions`/`education` are untouched, and the existing `SKILL_CAP` disclosure stands); and **the default post count moves 5 → 8**, which an honest cue makes affordable. A trimmed list now closes with one line in the theme's system voice — mono, accent, above a dotted rule that replaces the list's own last border: `8 of 342 · the rest on Bluesky ↗`. **The count turned out to be free**: both adapters already page the entire collection (`listAllRecords`, 10 × 100) before slicing, so they now return it — `fetchBlueskyPosts` → `{ posts, total }`, `fetchDocuments` → `{ documents, total, publication? }` (replies count towards neither). **The line only renders when there is somewhere to send the reader** — a count with no destination is a tease, not an affordance. Posts always have one, the owner's Bluesky profile by DID, matching the two links `SiteLayout` already builds. Writing has one when every listed document belongs to a single publication that publishes an address; documents spread across publications, or `pub.leaflet.*` legacy records which name none, get no line, and the label is the publication's name falling back to its host. Resolving that publication costs a read, so it is only attempted once a listing is known to be short, the two things wanting publication records now share one read instead of two, and a failed read costs the cue rather than the listing. No lexicon change, no record migration, no new route. 7 new tests (224 in `web`); verified in a browser through a temporary preview route covering posts-trimmed, posts-whole, writing-with-a-named-publication and writing-falling-back-to-its-host in both schemes, deleted before commit — accent-on-paper measured 6.58:1 light / 5.46:1 dark, 36px tall, no horizontal overflow at 375px. Smoke-tested against the deployed origin, where the cue is exercised by **three real sites**: `malpercio.mooring.page` and `mooring.malpercio.dev` (same DID, so both read **8 of 135 · the rest on Bluesky ↗**, pointing at `bsky.app/profile/did:web:malpercio.dev`) and `mooring.page/s/timheffley.xyz` (**8 of 194**); the served `SiteSections` stylesheet carries both `.more-line` rules — in production that is an external `_app/immutable/assets/*.css`, not inlined as in dev, so grep the stylesheet rather than the HTML. `jzweifel.dev` is the negative control: exactly 8 non-reply posts (12 records, 4 of them replies), so nothing is trimmed and it correctly shows no cue. **Two corrections to what the PR body claims.** It says `jzweifel.dev` has five posts and renders unchanged, and that no deployed site exercises the cue; both are wrong. `grep -c` counts matching *lines*, and server-rendered HTML is effectively one line — so "5" was an artifact of the measurement, not a post count. The raised default did change that site (5 shown → 8), and the cue had live coverage from the moment it deployed. **Method note for future smoke tests:** count occurrences with `grep -o … | wc -l`, and count top-level posts by `aria-label="View on Bluesky` rather than by `/post/` permalinks — quoted-post embeds carry permalinks too and inflate the count. - 2026-08-27 — **Bluesky post embeds render instead of a cue** (PR #41, **merged and deployed 2026-08-27**). Posts carrying an embed showed "· view on Bluesky" next to the date; critique 4 read the inconsistency as a glitch. Three of the four embed kinds turn out to cost nothing: `app.bsky.embed.external` and `app.bsky.embed.images` carry everything in the post record, so extraction is a pure function (`postEmbeds` in `atmosphere/bluesky.ts`, defensively parsed like the facet code beside it) and `recordWithMedia` is just both. Only `app.bsky.embed.record` needs the network. Jacob's calls: **hydrate quotes tolerantly** and **give images the full text measure with a height cap**. Hydration resolves the quoted author's DID document (one fetch yields both the PDS and the handle via `handleFromDidDocument`) then reads the post, all under a 2.5s `AbortSignal.timeout`, in parallel and only for the posts actually shown; any failure leaves a bare "Quoted post ↗" link. A self-quote skips the DID resolution entirely and renders without a byline. A quote that can neither be read nor linked — a feed generator, a list — is dropped so the post falls back to the Bluesky cue, which is now shown only for embed kinds the theme doesn't render (video, today). Theme work is one `postEmbed` snippet in `SiteSections.svelte`: link cards clamp title and blurb to two lines over a sunk-paper panel with the host in mono; a single image uses the record's `aspectRatio` as `width`/`height` attributes so the browser reserves the space, then `max-height: 22rem` does the fitting; two or more become a square-celled contact sheet. Two things fell out of the work: `AtmosphereContext` gained an optional `signal` that `xrpcGet` honors, and **`postPermalink` was too permissive** — it built a `/post/` URL for any collection, which would have produced broken links for non-post quote targets, so it now matches `app.bsky.feed.post` only (one guard, all callers). The existing `/blob` widths cover both a 4.5rem card thumb and a full-measure image, so the anticipated new thumb size wasn't needed. 16 new tests (217 in `web`); the recordWithMedia test caught a real nesting bug — that embed wraps its quote one level deeper than a bare `record` embed. Verified in a browser against live PDS data (`jzweifel.dev`: two real link cards with PDS thumbs, and the `recordWithMedia` post rendering its card plus a hydrated self-quote, cue gone, no overflow at 375px) and against a temporary preview route covering all seven states in both schemes, deleted before commit. Smoke-tested against the deployed origin: `mooring.page/s/jzweifel.dev` serves two link cards with their titles and hosts, and the `recordWithMedia` post renders its card plus the hydrated self-quote — the quoted text is in the server-rendered HTML with no `` and the right permalink, so the cross-network quote read runs fine from the Worker. No `view on Bluesky` cue anywhere on the page, and the per-post `aria-label` is intact. The card thumbs serve from `/blob/…?w=256` as **7.9 KB webp** (272 KB unresized jpeg in dev), so the zone's image transform covers the new thumb size without a widening of the allowlist. **One gap:** no live record exercises `app.bsky.embed.images` or a cross-author quote yet — both are covered by tests and the dev preview route, not by a deployed site.