From b8da84ae397692f30e627b2e241cd395eb71304c Mon Sep 17 00:00:00 2001 From: Jacob Zweifel Date: Tue, 25 Aug 2026 09:08:18 -0400 Subject: [PATCH] Per-site favicons, sorted career records, and disclosure fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every page served the SvelteKit scaffold's Svelte-logo favicon from the root layout. Rendered sites now set their own icon — the site record's icon blob, falling back to the profile avatar, served through /blob — and app pages get an airmail-striped icon in the theme's palette. The root layout only emits the app icon when no site is being rendered. Career records rendered in raw PDS record order; reverse-chronology was insertion-order luck. The sifa adapter now sorts positions and education newest-first by a key extracted from the formless date strings, undated entries last. The theme's year helpers moved to date-format.ts and match the first 4-digit year instead of slicing, so "Aug 2019" renders as "2019" rather than "Aug ". The career-summary disclosure hid its own summary once opened, which made it impossible to collapse and dropped keyboard focus onto a hidden node. The summary now stays visible and swaps its label between More and Less. Also: the contact address shows as visible text in the footer, so the mailto affordances have a fallback on machines with no mail client; the banner carries a srcset with a new 768 blob width (and the postcard variant requests 768); og:image points at the /blob route as an absolute URL instead of hotlinking the PDS; and site pages declare color-scheme and per-scheme theme-color. 10 new unit tests (188 in web). Co-Authored-By: Claude Fable 5 --- apps/web/src/lib/assets/favicon.svg | 11 ++++- apps/web/src/lib/blob-proxy.ts | 2 +- .../lib/components/theme/SiteLayout.svelte | 11 +++++ .../lib/components/theme/SiteSections.svelte | 43 ++++++++-------- apps/web/src/lib/date-format.ts | 15 ++++++ .../lib/server/atmosphere/atmosphere.test.ts | 15 ++++++ apps/web/src/lib/server/atmosphere/index.ts | 2 +- apps/web/src/lib/server/atmosphere/sifa.ts | 49 +++++++++++++------ apps/web/src/lib/server/render/site.ts | 12 +++++ apps/web/src/lib/site-view.ts | 2 + apps/web/src/lib/text-display.test.ts | 24 ++++++++- apps/web/src/routes/+layout.svelte | 6 ++- apps/web/src/routes/+page.svelte | 17 ++++++- apps/web/src/routes/s/[handle]/+page.svelte | 16 +++++- docs/NEXT.md | 12 ++--- 15 files changed, 186 insertions(+), 51 deletions(-) diff --git a/apps/web/src/lib/assets/favicon.svg b/apps/web/src/lib/assets/favicon.svg index cc5dc66..3805101 100644 --- a/apps/web/src/lib/assets/favicon.svg +++ b/apps/web/src/lib/assets/favicon.svg @@ -1 +1,10 @@ -svelte-logo \ No newline at end of file + + + + + + + + + + diff --git a/apps/web/src/lib/blob-proxy.ts b/apps/web/src/lib/blob-proxy.ts index ac4ac0c..02d5690 100644 --- a/apps/web/src/lib/blob-proxy.ts +++ b/apps/web/src/lib/blob-proxy.ts @@ -3,7 +3,7 @@ * blob and applies image transformations where the platform provides them. */ -const WIDTHS = [256, 1600] as const; +const WIDTHS = [256, 768, 1600] as const; export type BlobWidth = (typeof WIDTHS)[number]; export function isBlobWidth(value: number): value is BlobWidth { diff --git a/apps/web/src/lib/components/theme/SiteLayout.svelte b/apps/web/src/lib/components/theme/SiteLayout.svelte index 3eab034..108df8f 100644 --- a/apps/web/src/lib/components/theme/SiteLayout.svelte +++ b/apps/web/src/lib/components/theme/SiteLayout.svelte @@ -1,6 +1,8 @@ + {#if site.colors.background} {@html ``} + + {:else} + + {/if} @@ -66,6 +73,9 @@ @@ -81,6 +91,7 @@ .site are invisible up here. */ :global(html) { background: #f7f3ea; + color-scheme: light dark; } @media (prefers-color-scheme: dark) { diff --git a/apps/web/src/lib/components/theme/SiteSections.svelte b/apps/web/src/lib/components/theme/SiteSections.svelte index b5d84f2..f71c4ef 100644 --- a/apps/web/src/lib/components/theme/SiteSections.svelte +++ b/apps/web/src/lib/components/theme/SiteSections.svelte @@ -1,7 +1,7 @@ - + + {#if !page.data.site} + + {/if} {@render children()} diff --git a/apps/web/src/routes/+page.svelte b/apps/web/src/routes/+page.svelte index 445cada..e9aff0f 100644 --- a/apps/web/src/routes/+page.svelte +++ b/apps/web/src/routes/+page.svelte @@ -1,9 +1,22 @@ @@ -15,8 +28,8 @@ {/if} - {#if data.site.profile?.bannerUrl ?? data.site.avatarUrl} - + {#if ogImage} + {/if} {#if !data.site.discoverable} diff --git a/apps/web/src/routes/s/[handle]/+page.svelte b/apps/web/src/routes/s/[handle]/+page.svelte index 8fc6ca0..3394cd1 100644 --- a/apps/web/src/routes/s/[handle]/+page.svelte +++ b/apps/web/src/routes/s/[handle]/+page.svelte @@ -1,9 +1,21 @@ @@ -14,8 +26,8 @@ {/if} - {#if data.site.profile?.bannerUrl ?? data.site.avatarUrl} - + {#if ogImage} + {/if} {#if !data.site.discoverable} diff --git a/docs/NEXT.md b/docs/NEXT.md index 1f91c88..7c421c4 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-25 (**PR #22 merged** — contact CTA, career clamp, blob proxy (ADR 0015 Accepted), polish sweep. Awaiting Jacob-side steps: deploy, contactEmail in /admin, Image Transformations zone toggle, /about content, lexicon republication)._ +_Last updated: 2026-08-25 (**all operator steps done** — deployed, contactEmail set, Image Transformations live (avatar 966KB→17KB webp), /about written, lexicons republished. Critique 3 ran: 25 → 31 → 29, the dip being newly discovered pre-existing issues; the mechanical fixes for them are in the current PR)._ ## Where things stand @@ -14,12 +14,10 @@ Feasibility is done and the verdict was **build it** (see `FEASIBILITY.md`). All Done so far: OAuth login (loopback dev client; hosted-client path ready pending a real key + deploy) with D1-backed state/session stores; lexicon convention tests; **read-only adapters** for Bluesky (profile + posts, replies filtered), standard.site (documents/publications, `pub.leaflet.document` fallback only when no standard.site docs exist), and sifa (profile/positions/education/skills, defensively parsed) — fetch-injected modules in `apps/web/src/lib/server/atmosphere/` with unit tests, plus `detectSources` (drives the ADR 0012 default section order) and a source-overview admin page; **site/page authoring** — record builders, PDS writes through the OAuth session, and the `/admin` + `/admin/pages` CRUD routes; **the professional-presence theme** — the render pipeline in `apps/web/src/lib/server/render/` and the public routes at `/s/[handle]`. Remaining, roughly in dependency order: -- **Theme UX overhaul, remaining tail** (the overhaul landed 2026-08-24 and re-critiqued at **31/40** (from 25) — snapshots in `.impeccable/critique/`; the follow-up round landed 2026-08-25 as PR #22): - 1. *Content* — replace the "TODO TODO" placeholder on jzweifel.dev/about; the reference instance demos the product. Also: set `contactEmail` in /admin so the new "Write to me" / "Say hello →" affordances render (they answer the re-critique's P0 — "Open to" advertised availability with no way to act). - 2. *Enable Image Transformations on the mooring.page zone* (dashboard toggle) so the new `/blob` route (ADR 0015, Accepted 2026-08-25) actually resizes — until then it proxies originals (≈1.45MB of hero imagery per first view). - 3. *Lexicon republication* — `heroSection.style` and the site record's `contactEmail` are new optional fields; republish the on-network schema when convenient (runbook §§3–4). - 4. *Record-level career curation fields* (decided 2026-08-24, staged): the template now clamps the sifa summary and caps skills, but curation (hide/order/feature for positions, education, skills — e.g. de-emphasizing a high-school entry or dropping generic skills) should become `careerSection` fields edited in admin, once the admin UI grows a section editor. - 5. *Re-critique after the next deploy*; residual known gaps: no RSS, mono-metadata density (flat 10.9–11.5px cluster — the detector's one finding), postcard hero unexercised by a real record, career-before-writing default order questioned when posts carry the personality. +- **Theme UX overhaul, remaining tail** (critique trend 25 → 31 → 29 across three dual-assessment rounds — snapshots in `.impeccable/critique/`; the 29 reflects newly *discovered* pre-existing issues, not regression, and the current PR fixes the mechanical ones: per-site favicon (was the stock Svelte logo — the round-3 P0), career records sorted newest-first (was raw PDS record order), `yearSpan` tolerant of sifa's formless date strings, the career "More" disclosure made reversible, visible contact address in the footer, banner `srcset` + 768 blob width, absolute `og:image` through /blob, `color-scheme`/`theme-color` metas): + 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. The round-3 review sharpened the framing: "honest by default, curatable by choice." + 2. *Design questions queued from round 3*: an airmail **sign-off block** closing the page (postmark/restated contact/handle — the metaphor currently peaks in the first 300px and the page ends unresolved); whether email should be the only action on an Atmosphere-native product (no follow/DM intent anywhere); scheme-aware site color overrides (a single background override pins both schemes); mono-metadata density (the detector's standing flat-type finding). + 3. *Residual*: no RSS; postcard hero still unexercised by a real record. - **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. 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. - Later, when token-refresh races become real: a Durable-Object `requestLock` for the OAuth client. -- 2.51.2