Skip to content

feat(web): add social previews and template credit - #14

Merged
eduardstan merged 10 commits into
mainfrom
fm/lp-social-preview
Jul 30, 2026
Merged

eduardstan merged 10 commits into
mainfrom
fm/lp-social-preview

Conversation

@eduardstan

Copy link
Copy Markdown
Owner

Intent

Add OpenGraph and Twitter social preview meta tags to BaseLayout and add a discreet template credit link in Footer

What Changed

  • Add canonical Open Graph and Twitter summary metadata, using raster profile portraits for social images and omitting tags from alternate canonical views.
  • Add a discreet “Built with ledgerpress” credit link to the site footer.
  • Switch the example portrait to PNG, document supported social-image formats, and extend deployment-base checks for metadata and footer behavior.

Risk Assessment

✅ Low: The requested social metadata behavior, canonical-page suppression, raster filtering, bundled preview image, documentation, and footer credit are implemented coherently; only one minor example-description inconsistency remains.

Testing

No baseline results were supplied. After restoring local dependencies, the focused production-build check and live Chrome verification passed for project-subpath social metadata, alternate-view suppression, the raster preview image, and the corrected discreet footer credit; rendered HTML and screenshot evidence were captured, and transient dependencies/build outputs were removed.

  • Evidence: Rendered footer with discreet ledgerpress credit (local file: /tmp/no-mistakes-evidence/01KYSS959ZAG2ZP2CMSAQEQBPV/footer-credit.png)
Evidence: Rendered home HTML containing social metadata
<!DOCTYPE html><html lang="en" data-theme="light"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Sahana Aster KŌWHAI, Ph.D.</title><meta name="description" content="School of Earth and Ice Studies, Tideglass Research Cooperative. Ocean archives, chronology and uncertainty, open polar practice."><link rel="canonical" href="http://127.0.0.1:4322/ledgerpress/"><meta property="og:type" content="website"><meta property="og:title" content="Sahana Aster KŌWHAI, Ph.D."><meta property="og:description" content="School of Earth and Ice Studies, Tideglass Research Cooperative. Ocean archives, chronology and uncertainty, open polar practice."><meta property="og:url" content="http://127.0.0.1:4322/ledgerpress/"><meta property="og:site_name" content="Sahana Aster KŌWHAI, Ph.D."><meta name="twitter:card" content="summary"><meta property="og:image" content="https://eduardstan.github.io/ledgerpress/media/portrait.png"><meta name="twitter:image" content="https://eduardstan.github.io/ledgerpress/media/portrait.png"><link rel="icon" href="/ledgerpress/media/favicon.svg"><link rel="alternate" type="application/rss+xml" title="Sahana Aster KŌWHAI, Ph.D." href="/ledgerpress/rss.xml"><link rel="preload" href="/ledgerpress/fonts/Archivo-Black.woff2" as="font" type="font/woff2" crossorigin><link rel="preload" href="/ledgerpress/fonts/LedgerSerif-Regular.woff2" as="font" type="font/woff2" crossorigin><script>
  (() => {
    let stored = null;
    try {
      stored = localStorage.getItem('theme');
    } catch {}
    const dark =
      stored === 'dark' ||
      (stored === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
    document.documentElement.dataset.theme = dark ? 'dark' : 'light';
  })();
</script><style data-vite-dev-id="/home/eduard/.no-mistakes/worktrees/10001f893208/01KYSS959ZAG2ZP2CMSAQEQBPV/web/src/styles/global.css">/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
  :root, :host {
    --font-sans: 'Ledger Serif', ui-serif, Georgia, serif;
    --font-mono: 'Go Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
    --spacing: 0.25rem;
    --default-font-family: var(--font-sans);
    --default-mono-font-family: var(--font-mono);
    --color-page: #ffffff;
    --color-ink: #0a0a0a;
    --color-muted: #565656;
    --color-rule: #cbcbcb;
    --color-hair: #d9d9d9;
    --color-record: #f4f5fa;
    --color-accent: #1231d6;
    --font-display: 'Archivo Black', ui-sans-serif, system-ui, sans-serif;
  }
}
@layer base {
  *, ::after, ::before, ::backdrop, ::file-selector-button {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    border: 0 solid;
  }
  html, :host {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    tab-size: 4;
    font-family: var(--default-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");
    font-feature-settings: var(--default-font-feature-settings, normal);
    font-variation-settings: var(--default-font-variation-settings, normal);
    -webkit-tap-highlight-color: transparent;
  }
  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }
  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }
  h1, h2, h3, h4, h5, h6 {
    font-size: inherit;
    font-weight: inherit;
  }
  a {
    color: inherit;
    -webkit-text-decoration: inherit;
    text-decoration: inherit;
  }
  b, strong {
    font-weight: bolder;
  }
  code, kbd, samp, pre {
    font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);
    font-feature-settings: var(--default-mono-font-feature-settings, normal);
    font-variation-settings: var(--default-mono-font-variation-settings, normal);
    font-size: 1em;
  }
  small {
    font-size: 80%;
  }
  sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  sub {
    bottom: -0.25em;
  }
  sup {
    top: -0.5em;
  }
  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }
  :-moz-focusring:where(:not(iframe)) {
    outline: auto;
  }
  progress {
    vertical-align: baseline;
  }
  summary {
    display: list-item;
  }
  ol, ul, menu {
    list-style: none;
  }
  img, svg, video, canvas, audio, iframe, embed, object {
    display: block;
    vertical-align: middle;
  }
  img, video {
    max-width: 100%;
    height: auto;
  }
  button, input, select, optgroup, textarea, ::file-selector-button {
    font: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    letter-spacing: inherit;
    color: inherit;
    border-radius: 0;
    background-color: transparent;
    opacity: 1;
  }
  :where(select:is([multiple], [size])) optgroup {
    font-weight: bolder;
  }
  :where(select:is([multiple], [size])) optgroup option {
    padding-inline-start: 20px;
  }
  ::file-selector-button {
    margin-inline-end: 4px;
  }
  ::placeholder {
    opacity: 1;
  }
  @supports (not (-webkit-appearance: -apple-pay-button))  or (contain-intrinsic-size: 1px) {
    ::placeholder {
      color: currentcolor;
      @supports (color: color-mix(in lab, red, red)) {
        color: color-mix(in oklab, currentcolor 50%, transparent);
      }
    }
  }
  textarea {
    resize: vertical;
  }
  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }
  ::-webkit-date-and-time-value {
    min-height: 1lh;
    text-align: inherit;
  }
  ::-webkit-datetime-edit {
    display: inline-flex;
  }
  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }
  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
    padding-block: 0;
  }
  ::-webkit-calendar-picker-indicator {
    line-height: 1;
  }
  :-moz-ui-invalid {
    box-shadow: none;
  }
  button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button {
    appearance: button;
  }
  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
    height: auto;
  }
  [hidden]:where(:not([hidden="until-found"])) {
    display: none !important;
  }
}
@layer utilities {
  .collapse {
    visibility: collapse;
  }
  .visible {
    visibility: visible;
  }
  .absolute {
    position: absolute;
  }
  .fixed {
    position: fixed;
  }
  .relative {
    position: relative;
  }
  .static {
    position: static;
  }
  .mt-4 {
    margin-top: calc(var(--spacing) * 4);
  }
  .mt-6 {
    margin-top: calc(var(--spacing) * 6);
  }
  .block {
    display: block;
  }
  .grid {
    display: grid;
  }
  .hidden {
    display: none;
  }
  .inline {
    display: inline;
  }
  .table {
    display: table;
  }
  .text-muted {
    color: var(--color-muted);
  }
  .italic {
    font-style: italic;
  }
  .ring {
    --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
    box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
  }
  .outline {
    outline-style: var(--tw-outline-style);
    outline-width: 1px;
  }
  .filter {
    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
  }
  .dark\:hidden:where([data-theme='dark'], [data-theme='dark'] *) {
    display: none;
  }
  .dark\:inline:where([data-theme='dark'], [data-theme='dark'] *) {
    display: inline;
  }
}
@font-face {
  font-display: block;
  font-family: KaTeX_AMS;
  font-style: normal;
  font-weight: 400;
  src: url(/node_modules/katex/dist/fonts/KaTeX_AMS-Regular.woff2)

... [59182 bytes truncated] ...

nt/publications.bib and the teaching record.</p><p><b>Open polar practice</b> — content/cv.yaml · strands[2] · Evidence lives in the projects and service sections.</p></div></div><div class="col"><h2 class="sec">The count<span class="record-of" data-pagefind-ignore>content/publications.bib</span></h2><ul class="stats"><li><span class="n">10</span><span class="l label">entries in this bibliography</span></li><li><span class="n">3</span><span class="l label">entries · Books</span></li><li><span class="n">2</span><span class="l label">entries · Under review</span></li><li><span class="n">2</span><span class="l label">entries · Journal</span></li><li><span class="n">1</span><span class="l label">entry · Data</span></li><li><span class="n">1</span><span class="l label">entry · Software</span></li><li><span class="n">1</span><span class="l label">entry · Conference</span></li><li><span class="n">2</span><span class="l label">editorial boards</span></li><li><span class="n">5</span><span class="l label">service roles on record</span></li></ul><div class="record" data-pagefind-ignore><p><b>content/publications.bib</b> — 10 entries, 2021–2028, counted at build by parsing the file. The two service figures are counted from <b>content/cv.yaml</b>: <b>2</b>entries of <b>service[]</b> whose <b>title</b> matches “editor” (Tideglass Climate Letters, Example Journal of Palaeoclimate Studies), and <b>5</b>entries in that list altogether.</p></div><h2 class="sec" id="service">Associate Editor<span class="record-of" data-pagefind-ignore>content/cv.yaml · service[] · role: Associate Editor</span></h2><ul class="defs"><li><span class="a">Tideglass Climate Letters</span><span class="b label">2023 – Present</span></li><li><span class="a">Example Journal of Palaeoclimate Studies<a class="rank" href="https://example.org/journals/palaeoclimate-studies">IF: 3.4, Q1</a></span><span class="b label">2022, 2023, 2024</span></li></ul><h2 class="sec">Scientific Steering Committee<span class="record-of" data-pagefind-ignore>content/cv.yaml · service[] · role: Scientific Steering Committee</span></h2><ul class="defs"><li><span class="a">Tideglass Research Cooperative</span><span class="b label">2022, 2023, 2024, 2025, 2026</span></li></ul><h2 class="sec">Panel reviewer<span class="record-of" data-pagefind-ignore>content/cv.yaml · service[] · role: Panel reviewer</span></h2><ul class="defs"><li><span class="a">Kōwhai Research Trust</span><span class="b label">2021 – Present</span></li></ul><h2 class="sec">Reviewer<span class="record-of" data-pagefind-ignore>content/cv.yaml · service[] · role: Reviewer</span></h2><ul class="defs"><li><span class="a">Open Core Review</span></li></ul><div class="record" data-pagefind-ignore><p><b>content/cv.yaml</b> — <b>service[]</b>, 5 entries grouped into 4 roles by their own <b>title</b> field (Associate Editor: 2, Scientific Steering Committee: 1, Panel reviewer: 1, Reviewer: 1), in the order those roles first appear in the file. The meta on each row is the entry's <b>dates</b> where it states a term and its <b>years[]</b> where it states editions; the badges are the <b>metric</b> field, linked to the <b>rank_url</b> beside it.</p><p>This column and <a href="/ledgerpress/service/">/service/</a> read the same list through the same grouping in <b>web/src/lib/cv-schema.ts</b>, so they cannot disagree. They once came from two files that had drifted apart; there is one file now, and it is the one the printed CV is generated from.</p></div><div class="record" data-pagefind-ignore><p><b>web/src/lib/consistency.ts</b> — the consistency gate: 1 check · 9 comparisons · 0 contradictions · 0 excused · 0 records with nothing to compare against. Each check compares two hand-typed records of one fact — a date the fact states and the date it says it was announced on — joined by being the same entry rather than by matching prose. A contradiction refuses <code>astro build</code>; <code>astro dev</code> never fails, so this block is where the author sees one.</p><p><b>announced-in-own-year</b> — an announcement date must fall in the year of the fact it hangs on, or the year before it (invited in N−1 to serve at the N edition). Over content/cv.yaml, content/publications.bib.</p></div></div><div class="col"><h2 class="sec">Lately<span class="record-of" data-pagefind-ignore>content/cv.yaml</span></h2><ul class="feed"><li><span class="when label"><time datetime="2026-07-14">14 Jul 2026</time> · Under review</span><p><b>Sea-ice retreat and productivity at the East Antarctic margin</b>, submitted to Tāwhirimātea Ice Records.</p></li><li><span class="when label"><time datetime="2026-06-18">18 Jun 2026</time> · Invited keynote</span><p><i>What an open sediment record should remember</i>, Tideglass Earth Assembly, Bergen, Norway.</p></li><li><span class="when label"><time datetime="2026-02-12">12 Feb 2026</time> · Writing</span><p><b><a href="/ledgerpress/blog/2026/reading-a-core/">Reading a sediment core without hiding the joins</a></b>.</p></li><li><span class="when label"><time datetime="2026">2026</time> · Journal</span><p><b>Southern Ocean front migration recorded across two glacial terminations</b>, Koru Basin Records.</p></li><li><span class="when label"><time datetime="2025-09">Sept 2025</time> · Outreach</span><p><b>Sediment stories: reading climate from ocean cores</b>, <a href="https://learning.example.edu/sediment-stories">Kōwhai Coast Learning Collective</a>.</p></li><li><span class="when label"><time datetime="2025-08-21">21 Aug 2025</time> · Oral presentation</span><p><i>First results from the Pukaki Rise coring transect</i>, Tāwhirimātea Polar Forum, Te Whanganui-a-Tara Wellington, Aotearoa New Zealand.</p></li></ul><p class="more-feed"><a class="label" href="/ledgerpress/lately/">All 26 items →</a></p><div class="record" data-pagefind-ignore><p>26 announcements, generated from the facts themselves — <b>content/cv.yaml</b>, <b>content/publications.bib</b>, <b>content/talks.bib</b>, <b>content/posts/**/*.{md,mdx}</b>. Nothing is written twice: a fact is announced on the date it carries, and the feed shows that date at the precision the source states it and no finer.</p><p>This column is capped at 6, so 6 of 26 announcements are shown here and 20 are not; the full feed is at <a href="/ledgerpress/lately/">/lately/</a>.</p></div></div></div></main><footer class="sheet-footer" data-pagefind-ignore><div class="footer-grid"><div><h2 class="label">Contact</h2><a href="mailto:sahana.kowhai@example.edu">sahana.kowhai@example.edu</a><div class="record" data-pagefind-ignore><p><b>content/cv.yaml</b> — <code>profile.email</code> and <code>profile.name</code>.</p></div></div><div><h2 class="label">Elsewhere</h2><div class="stack"><a href="https://orcid.org/0000-0000-0000-0000">ORCID · 0000-0000-0000-0000</a><a href="https://scholar.google.com/citations?user=sahana-kowhai-example">Google Scholar · sahana-kowhai-example</a><a href="https://github.com/sahana-kowhai">GitHub · sahana-kowhai</a><a href="https://www.linkedin.com/in/sahana-kowhai">LinkedIn · sahana-kowhai</a><a href="https://bsky.app/profile/sahana-kowhai.example">Bluesky</a><a href="/ledgerpress/rss.xml">RSS</a><a href="/ledgerpress/sitemap-index.xml">Sitemap</a></div><div class="record" data-pagefind-ignore><p><b>content/cv.yaml</b> — one link per <code>profile.links</code> entry. Scholar, ORCID, GitHub and LinkedIn may be compact IDs whose addresses are derived; any other service is a <code>{ label, url }</code> pair used by both outputs.</p></div></div><div><h2 class="label">Post</h2><address>Tideglass Research Cooperative<br>Kārearea University<br>School of Earth and Ice Studies<br>360 Leith Street<br>Ōtepoti Dunedin, Aotearoa New Zealand<br></address><div class="record" data-pagefind-ignore><p><b>content/cv.yaml · profile</b> — <code>affiliation[]</code> broadest first, then <code>address[]</code> and <code>place</code>.</p></div></div></div><p class="label colophon">&copy; 2026 Sahana Aster KŌWHAI, Ph.D. &middot; Built with <a href="https://github.com/eduardstan/ledgerpress">ledgerpress</a></p></footer></div></body></html>
Evidence: Live browser verification
OpenGraph type, title, description, URL, site name, and portrait image were present. Twitter emitted a summary card and portrait image. The visible 11px footer credit links to https://github.com/eduardstan/ledgerpress.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ web/src/layouts/BaseLayout.astro:44 - Pages such as /publications/year-asc/ pass a variant-specific title while canonicalizing to /publications/. The resulting cards share one og:url but advertise different og:title values, so crawler order can determine the canonical page’s preview title. Decide on canonical social metadata and expose it separately from the browser title at this shared layout boundary.
  • ⚠️ web/src/layouts/BaseLayout.astro:49 - The bundled and adopter-fixture portraits are SVG files, but Twitter/X image cards do not support SVG. Consequently, the template’s default twitter:image will not render. Consider a crawler-compatible raster social-image field or omit unsupported image tags.

🔧 Fix: Fix canonical social metadata and raster previews
1 info still open:

  • ℹ️ content/cv.yaml:31 - Changing the bundled profile to portrait.png leaves README.md’s example description claiming that the record exercises an SVG portrait. Update that sentence to describe the raster portrait and, if useful, note that its SVG source remains alongside it.
✅ **Test** - passed

✅ No issues found.

  • npm ci and npm ci --prefix web to restore this isolated worktree’s locked test dependencies
  • npm run check:deployment-base — reproduced the new footer assertion failure, then passed after the fix
  • Rendered http://127.0.0.1:4322/ledgerpress/ in Chrome at 1440×1000 and visually inspected the footer
  • Evaluated live OpenGraph/Twitter metadata plus the footer link target, visibility, and 11px colophon styling in Chrome
  • curl --fail --silent --show-error http://127.0.0.1:4322/ledgerpress/ to capture the final rendered HTML
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@eduardstan
eduardstan force-pushed the fm/lp-social-preview branch from 3a2777e to 9adab21 Compare July 30, 2026 15:57
@eduardstan
eduardstan force-pushed the fm/lp-social-preview branch from 9adab21 to a7527fa Compare July 30, 2026 16:25
@eduardstan
eduardstan merged commit 4c6e8f2 into main Jul 30, 2026
3 checks passed
eduardstan added a commit that referenced this pull request Jul 31, 2026
* Add OpenGraph and Twitter social preview meta tags and template footer link

* no-mistakes(review): Fix canonical social metadata and raster previews

* no-mistakes(document): Document social metadata and format changed files

* docs: update README.md to reflect example portrait PNG format

* no-mistakes(review): Drop footer credit assertion from deployment-base check

* no-mistakes(document): Document social previews and footer credit line

* test: drop footer copyright year assertion from check-deployment-base

* no-mistakes(review): emit og:type=article on blog posts only

* no-mistakes(review): match blog posts relative to deployment base

* no-mistakes(document): clarify portrait format in content schema comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant