From fcb0190a80ba0865c32572224920ac60d964a42d Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 14:08:59 -0600 Subject: [PATCH 01/14] chore(styles): delete stale styles file --- ...387c4d518caa2d6c9a0b147545a9ec70e3c0dd.css | 650 ------------------ 1 file changed, 650 deletions(-) delete mode 100644 styles.39f33a92872513c6c724ed57ee387c4d518caa2d6c9a0b147545a9ec70e3c0dd.css diff --git a/styles.39f33a92872513c6c724ed57ee387c4d518caa2d6c9a0b147545a9ec70e3c0dd.css b/styles.39f33a92872513c6c724ed57ee387c4d518caa2d6c9a0b147545a9ec70e3c0dd.css deleted file mode 100644 index 72e9007..0000000 --- a/styles.39f33a92872513c6c724ed57ee387c4d518caa2d6c9a0b147545a9ec70e3c0dd.css +++ /dev/null @@ -1,650 +0,0 @@ -/* -I based the Anthesis Legible fonts on Atkinson Hyperlegible Next/Mono, -The designers of those typefaces did all the hard work, not me. - -I renamed these fonts due to a licensing issue: the way I subset -fonts causes them to become "modified" under SIL OFL 1.1, so I can't -use the same name if a Reserved Font Name is set. - -https://openfontlicense.org/ -*/ - -* { - margin: 0; - padding: 0; -} -*, -*::before, -*::after { - box-sizing: border-box; -} -html { - -moz-text-size-adjust: none; - -webkit-text-size-adjust: none; - text-size-adjust: none; -} -p, -h1, -h2, -h3, -h4, -h5, -h6 { - overflow-wrap: break-word; -} -@supports (text-wrap: balance) { - :where(h1, h2, h3, h4, h5, h6, figcaption, blockquote footer, cite, dt):where( - :not(body > header *, body > footer *, nav *, menu *, button *) - ) { - text-wrap: balance; - } -} -@supports (text-wrap: pretty) { - :where(p, li, dd, blockquote p):where( - :not(body > header *, body > footer *, nav *, menu *, button *) - ) { - text-wrap: pretty; - } -} - -.fonts-loaded-1 body { - font-family: "Anthesis Legible Sans Faux", sans-serif; -} -.fonts-loaded-1 :is(code, kbd, pre, samp, tt, var) { - font-family: "Anthesis Legible Mono Faux", monospace; -} -.fonts-loaded-1 :is(h1, h2, h3, h4, h5, h6) { - font-family: "Anthesis Legible Sans Faux", sans-serif; -} -.fonts-loaded-2 body { - font-family: "Anthesis Legible Sans", sans-serif; -} -.fonts-loaded-2 :is(code, kbd, pre, samp, tt, var) { - font-family: "Anthesis Legible Mono", monospace; -} -.fonts-loaded-2 :is(h1, h2, h3, h4, h5, h6) { - font-family: "Anthesis Legible Sans", sans-serif; -} -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-critical-text.54375ca3dae348f0c57a53aafc73d794efcff78bd00a1b55102111a29f55d862.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans Faux"; - font-style: normal; - font-weight: 400; - unicode-range: - U+20-23, U+25-29, U+2C-3B, U+3F-5A, U+61-7A, U+2013-2014, U+2018-2019, - U+201C-201D, U+2022, U+2026; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-critical-code.a0447452cb2d31877e08bfe3fa4d46d4a4e0a1fc29bebebc75be7b6c90943c0c.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono Faux"; - font-style: normal; - font-weight: 400; - unicode-range: U+20-7E; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-latin.c6f798dde43a67cb5856628c130610f8b149c1878dad65e35dbea86a64be0881.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+20-7E, U+A0-AC, U+AE-B4, U+B6-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, - U+2009, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, - U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-latin-extended.955152a44de0f377b6a77eb91be90946e53b93871138f230ac45c530bc2c4edd.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+100-107, U+10A-113, U+116-11B, U+11E-123, U+126-127, U+12A-12B, U+12E-133, - U+136-137, U+139-13E, U+141-148, U+150-155, U+158-15B, U+15E-165, U+16A-16B, - U+16E-17E, U+192, U+218-21B, U+237, U+1E80-1E85, U+1E9E, U+1EF2-1EF3, - U+2020, U+20B9, U+2113; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-greek.1cfd8452350d88206031e2046abc4db15ae6dec2b13b1a897cc70a8c298c76d9.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: normal; - font-weight: 200 800; - unicode-range: U+394, U+3A9, U+3BC, U+3C0; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-vietnamese.8311ac94dc2714361f24f8cc37758b7853ee4247750ac9866f64e4c84892f5b3.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: normal; - font-weight: 200 800; - unicode-range: U+102-103, U+110-111, U+1EF2-1EF3; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-other.031dfd9a4e3fd0e4404251b2c2b50bfcf8e4bc6f51f0a3fd797ae16b72e1d83a.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+2C7, U+2C9, U+2D8-2D9, U+2DB, U+2DD, U+300-304, U+306-308, U+30A-30C, - U+312, U+326-328, U+212E, U+2202, U+220F, U+2211, U+2219-221A, U+221E, - U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+266A; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-italic-latin.c61d509d81c50fa95ea5075f46303c49e4b37d1b4aca263beb23a416cd030011.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+20-7E, U+A0-AC, U+AE-B4, U+B6-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, - U+2009, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, - U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-italic-latin-extended.5cf0371616b92063f9b4763e4922521b38ff468fdb66989ed5bd51f19297b7d0.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+100-107, U+10A-113, U+116-11B, U+11E-123, U+126-127, U+12A-12B, U+12E-133, - U+136-137, U+139-13E, U+141-148, U+150-155, U+158-15B, U+15E-165, U+16A-16B, - U+16E-17E, U+192, U+218-21B, U+237, U+1E80-1E85, U+1E9E, U+1EF2-1EF3, - U+2020, U+20B9, U+2113; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-italic-greek.81984cd9ef07e369d38af2248c6471039214b7969d9a500319a627f513a4bdbd.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: italic; - font-weight: 200 800; - unicode-range: U+394, U+3A9, U+3BC, U+3C0; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-italic-vietnamese.3ff32f27caaa0f3970381da0b1d3c66ff5129728075126315f4a275f81101c94.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: italic; - font-weight: 200 800; - unicode-range: U+102-103, U+110-111, U+1EF2-1EF3; -} - -@font-face { - src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-italic-other.dc6034154650ab7d2fc6c64c8ac7898af54036b807138732541c06051ba199fc.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Sans"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+2C7, U+2C9, U+2D8-2D9, U+2DB, U+2DD, U+300-304, U+306-308, U+30A-30C, - U+312, U+326-328, U+212E, U+2202, U+220F, U+2211, U+2219-221A, U+221E, - U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+266A; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-latin-extended.521927f972b4f5a6da11d00fbbc2124d08fd60e874222006d25c592acea688aa.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+100-107, U+10A-113, U+116-11B, U+11E-123, U+126-127, U+12A-12B, U+12E-133, - U+136-137, U+139-13E, U+141-148, U+150-155, U+158-15B, U+15E-165, U+16A-16B, - U+16E-17E, U+192, U+218-21B, U+237, U+1E80-1E85, U+1E9E, U+1EF2-1EF3, U+2020; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-latin.2a602be2c1e52e20de28b2fd7cb6bc081c06927bc94af9e77d74079fc89cd9fe.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+20-7E, U+A0-AC, U+AE-B4, U+B6-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, - U+2009, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, - U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-greek.3c7262cfa78615b5e5440167184bf19d9752e1b509d5b65607918500964516aa.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: normal; - font-weight: 200 800; - unicode-range: U+394, U+3A9, U+3BC, U+3C0; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-vietnamese.6ebf548c4c44c90fcbbe66eb43f89128ae437a4142e8050273e8a8197436d607.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: normal; - font-weight: 200 800; - unicode-range: U+102-103, U+110-111, U+1EF2-1EF3; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-roman-other.6976da59eb68bd55a8eca7955d773c09bac9531c5bb1039a5eece6f3ea540114.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: normal; - font-weight: 200 800; - unicode-range: - U+2C7, U+2C9, U+2D8-2D9, U+2DB, U+2DD, U+300-304, U+306-308, U+30A-30C, - U+326-328, U+212E, U+2202, U+220F, U+2211, U+2219-221A, U+221E, U+222B, - U+2248, U+2260, U+2264-2265, U+25CA, U+266A; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-italic-latin.20e68301c11a62f15bed63a7733a12938e9c25de2f589d17550a2e1058a8007b.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+20-7E, U+A0-AC, U+AE-B4, U+B6-FF, U+131, U+152-153, U+2C6, U+2DA, U+2DC, - U+2009, U+2013-2014, U+2018-201A, U+201C-201E, U+2020-2022, U+2026, U+2030, - U+2039-203A, U+2044, U+20AC, U+2122, U+2212, U+2215; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-italic-latin-extended.445d5386629dd0e8a5c895025cb3d3f930bccf155bcb79f01afc7198aff6d608.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+100-107, U+10A-113, U+116-11B, U+11E-123, U+126-127, U+12A-12B, U+12E-133, - U+136-137, U+139-13E, U+141-148, U+150-155, U+158-15B, U+15E-165, U+16A-16B, - U+16E-17E, U+192, U+218-21B, U+237, U+1E80-1E85, U+1E9E, U+1EF2-1EF3, U+2020; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-italic-greek.e437835c6a4a3565a77b3d490b3a306ec4ff11dce6275e5a8d318e96bc97101a.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: italic; - font-weight: 200 800; - unicode-range: U+394, U+3A9, U+3BC, U+3C0; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-italic-vietnamese.23f3cc0818434f67c07e9fb235ccc39e141da2a534713142e75238e4a5d64a4c.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: italic; - font-weight: 200 800; - unicode-range: U+102-103, U+110-111, U+1EF2-1EF3; -} - -@font-face { - src: url("/fonts/anthesis-legible-mono/anthesis-legible-mono-italic-other.b7feead5cb879bde90955c64bcc6fa2a88dd3ff1178532d4c48655a7f46e7240.woff2") - format("woff2"); - font-display: swap; - font-family: "Anthesis Legible Mono"; - font-style: italic; - font-weight: 200 800; - unicode-range: - U+2C7, U+2C9, U+2D8-2D9, U+2DB, U+2DD, U+300-304, U+306-308, U+30A-30C, - U+326-328, U+212E, U+2202, U+220F, U+2211, U+2219-221A, U+221E, U+222B, - U+2248, U+2260, U+2264-2265, U+25CA, U+266A; -} - -:root { - --font-sans: sans-serif; - --font-mono: monospace; - --base-font-size: clamp(1.065rem, 0.973rem + 0.463vw, 1.331rem); - --bg-color: #fff3eb; - --fg-color: #1b1008; - --link-fg-color: #013a6b; - --visited-link-color: #4a275f; - --code-block-bg-color: #eee2da; - --code-block-fg-color: #100601; - --heading-fg-color: #53453c; - --hr-color: #53453c; - --footer-fg-color: #3f3229; -} - -@media (prefers-color-scheme: dark) { - :root { - --bg-color: #292c37; - --fg-color: #e5ebfa; - --link-fg-color: #cdf0ff; - --visited-link-color: #fae5ff; - --code-block-bg-color: #353945; - --code-block-fg-color: #eaf0fe; - --heading-fg-color: #ced3e0; - --hr-color: #ced3e0; - --footer-fg-color: #d9deed; - } -} - -html { - font-size: var(--base-font-size); -} - -body { - margin: auto; - padding-inline: clamp(0px, 288.842px + -42.105vw, 24px); - padding-block: 1rem; - font-family: var(--font-sans); - font-kerning: normal; - text-rendering: optimizeLegibility; - line-height: 1.5; - max-width: 34rem; - background-color: var(--bg-color); - color: var(--fg-color); -} - -p, -dl, -ol, -ul, -pre, -menu, -hr { - margin: 1em 0; -} - -blockquote, -figure { - margin: 1em 2.5em; -} - -blockquote { - font-size: 0.9375em; - line-height: 1.4; -} - -ul { - list-style-type: circle; -} -ul, -ol, -menu { - padding-left: 2.5em; -} - -dd { - margin-left: 2.5em; -} - -ul ul, -ol ol, -ul ol, -ol ul { - margin: 0; -} - -li + li { - margin-top: 0.5em; -} -li :is(ul, ol) li:first-of-type { - margin-top: 0.25em; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - color: var(--heading-fg-color); - font-family: var(--font-sans); -} - -h2, -h3, -h4, -h5, -h6 { - margin-top: 1.5em; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - font-weight: 525; - line-height: 1.25; -} - -/* Keep headings attached to their content when printing (gate 7). */ -h1, -h2, -h3, -h4, -h5, -h6 { - break-after: avoid; - break-inside: avoid; -} - -h2 + :is(h3, h4, h5, h6), -h2 + section :is(h3, h4, h5, h6), -h3 + :is(h4, h5, h6), -h3 + section :is(h4, h5, h6), -h4 + :is(h5, h6), -h4 + section :is(h5, h6), -h5 + h6, -h5 + section h6 { - margin-top: 0.25em; -} - -:is(h1, h2, h3, h4, h5, h6) + :not(section, h1, h2, h3, h4, h5, h6) { - margin-top: 0.75em; -} - -nav[class="articles"] :is(h1, h2, h3, h4, h5, h6) + ul { - margin-top: 0.25em; -} - -nav[class="articles"] li + li { - margin-top: 0.25em; -} - -code, -kbd, -pre, -samp, -tt, -var { - font-family: var(--font-mono); - font-size: 0.9em; - color: var(--code-block-fg-color); -} -pre code, -pre kbd, -pre samp, -pre tt, -pre var { - font-size: 1em; -} - -h1 { - font-size: clamp(1.25rem, 1.163rem + 0.435vw, 1.5rem); -} -h2 { - font-size: 1.25rem; -} -h3 { - font-size: 1.1rem; -} -h4, -h5, -h6 { - font-size: 1rem; -} - -img, -picture { - max-width: 100%; - height: auto; - display: block; - margin: 1.5em 0; -} - -p code, -p kbd, -li code, -li kbd { - background-color: var(--code-block-bg-color); -} - -ul li p { - margin-bottom: 0; -} - -pre { - overflow: auto; - padding: 1em; - background-color: var(--code-block-bg-color); - border-radius: 0.25em; -} - -.navbar ul { - display: flex; - flex-wrap: wrap; - justify-content: flex-start; - font-size: 1rem; - padding-left: 0; - margin-top: 0; - margin-bottom: 0.75em; - gap: 0.75em; -} - -.navbar li { - display: inline-block; - margin-top: 0; -} - -a, -a code { - color: var(--link-fg-color); -} - -:where(a[href]) { - text-decoration-line: underline; - text-decoration-thickness: 0.08em; - text-underline-offset: 0.16em; - text-decoration-skip-ink: auto; - text-decoration-color: currentColor; -} - -@supports (color: color-mix(in oklch, white, black)) { - :where(a[href]) { - text-decoration-color: color-mix(in oklch, currentColor 40%, transparent); - } -} - -:where(a[href]):hover { - text-decoration-color: currentColor; -} - -:where(a[href]):focus-visible { - text-decoration-color: currentColor; -} - -@media (prefers-reduced-motion: no-preference) { - :where(a[href]) { - transition: text-decoration-color 150ms ease-out; - } -} - -a code { - background-color: var(--bg-color); -} - -a:visited, -a:visited code { - color: var(--visited-link-color); -} - -.navbar a:visited { - color: var(--link-fg-color); -} - -a:focus, -summary:focus, -[tabindex="0"]:focus, -form :focus { - outline: 3px solid currentColor; -} - -@supports selector(:focus-visible) { - a:focus:not(:focus-visible), - [tabindex="0"]:focus:not(:focus-visible) { - outline: none; - } -} - -hr { - background-color: var(--hr-color); - height: 1px; - border: none; - opacity: 0.4; -} - -footer { - margin-top: 2.5em; - font-size: 0.9375em; - color: var(--footer-fg-color); -} -footer code { - color: var(--footer-fg-color); - word-break: break-all; -} -footer hr { - margin-bottom: 1.25em; -} -footer p { - margin-bottom: 0; -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} From 2485285cda6c6046d444c57fa5c0f6b28ca70dd7 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 14:54:03 -0600 Subject: [PATCH 02/14] chore(tooling): add editorconfig --- .editorconfig | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2898f30 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{css,js,json,jsonc,yml,yaml,sh}] +indent_style = space +indent_size = 2 + +[Makefile] +indent_style = tab + +# Vendored/generated trees and never-edit upstream files: leave bytes alone. +[{fonts,stagit,migration,pubkeys}/**] +insert_final_newline = false +trim_trailing_whitespace = false From 243993f06d0a37dc391cc6f8a20f415ddae6b879 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 14:55:33 -0600 Subject: [PATCH 03/14] chore(tooling): add pre-commit framework with hygiene hooks --- .pre-commit-config.yaml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..86fc170 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,23 @@ +# Quality gate for anthes.is. Hygiene fixers must never touch vendored +# trees or content-hashed assets (editing one silently breaks its hash) — +# the &generated anchor below excludes both. +default_stages: [pre-commit] + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + exclude: &generated '^(fonts/|stagit/|migration/|pubkeys/)|\.[0-9a-f]{64}\.' + - id: end-of-file-fixer + exclude: *generated + - id: mixed-line-ending + args: [--fix=lf] + exclude: *generated + - id: check-yaml + - id: check-json + exclude: '\.jsonc$' + - id: check-merge-conflict + - id: check-case-conflict + - id: check-added-large-files + args: [--maxkb=600] From 55f1103da405bd85d7a24d14e60b67a342b42b4e Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 14:59:32 -0600 Subject: [PATCH 04/14] fix(fonts): correct swapped hash tokens on sans roman latin subsets The latin and latin-extended woff2 files of Anthesis Legible Sans roman carried each other's content hash in their filenames: each file's actual sha256 matched the token on the other file. Glyph coverage confirms the semantic names are correct (latin holds basic Latin, latin-extended holds U+0100-024F), so only the hash tokens were crossed at hashing time. Rename both files to their true content hashes, update the @font-face URLs in the stylesheet, and rehash the stylesheet accordingly in _header.html and errdocs/err.html. --- _header.html | 2 +- errdocs/err.html | 2 +- ...c130610f8b149c1878dad65e35dbea86a64be0881.woff2} | Bin ...91be90946e53b93871138f230ac45c530bc2c4edd.woff2} | Bin ...c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css | 4 ++-- 5 files changed, 4 insertions(+), 4 deletions(-) rename fonts/anthesis-legible-sans/{anthesis-legible-sans-roman-latin-extended.955152a44de0f377b6a77eb91be90946e53b93871138f230ac45c530bc2c4edd.woff2 => anthesis-legible-sans-roman-latin-extended.c6f798dde43a67cb5856628c130610f8b149c1878dad65e35dbea86a64be0881.woff2} (100%) rename fonts/anthesis-legible-sans/{anthesis-legible-sans-roman-latin.c6f798dde43a67cb5856628c130610f8b149c1878dad65e35dbea86a64be0881.woff2 => anthesis-legible-sans-roman-latin.955152a44de0f377b6a77eb91be90946e53b93871138f230ac45c530bc2c4edd.woff2} (100%) rename styles.84d2a477bc0506677c24e4690e87812e05036b199b9632616b072558435db30b.css => styles.48c47559d0a9a2eb9160c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css (98%) diff --git a/_header.html b/_header.html index 9944663..da1d44d 100644 --- a/_header.html +++ b/_header.html @@ -6,7 +6,7 @@ Date: Fri, 12 Jun 2026 15:00:11 -0600 Subject: [PATCH 05/14] chore(tooling): add asset hash-integrity check POSIX sh script enforcing three invariants that were previously manual: filename hash tokens match file contents, every hashed asset referenced by _header.html, errdocs/err.html, and site.webmanifest exists on disk, and the two HTML files agree on the hash tokens they reference. Wired as a local pre-commit hook. This check is what surfaced the swapped font hash tokens fixed in the previous commit. --- .github/scripts/check-hashes.sh | 60 +++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 9 +++++ 2 files changed, 69 insertions(+) create mode 100755 .github/scripts/check-hashes.sh diff --git a/.github/scripts/check-hashes.sh b/.github/scripts/check-hashes.sh new file mode 100755 index 0000000..637f43d --- /dev/null +++ b/.github/scripts/check-hashes.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# check-hashes.sh — integrity checks for content-hashed assets. +# +# (a) every tracked file with a .. token in its name hashes to +# exactly that token; +# (b) every hashed asset referenced by _header.html, errdocs/err.html and +# site.webmanifest exists on disk; +# (c) _header.html and errdocs/err.html agree on the hash tokens they +# reference (compared by basename, since the two files may legitimately +# use different path prefixes for the same asset). +# +# Word-splitting over `git ls-files` output is safe here: tracked names in +# this repo contain no whitespace. + +set -eu + +cd "$(git rev-parse --show-toplevel)" + +status=0 +fail() { + printf 'check-hashes: %s\n' "$1" >&2 + status=1 +} + +# --- (a) filename hash matches content hash -------------------------------- +for f in $(git ls-files | grep -E '\.[0-9a-f]{64}\.' || true); do + # Extract the hash by pattern, not positional dot-splitting: several + # images carry a double suffix (name..2.png) that naive ${f%.*} + # parsing mishandles. + want=$(expr "$f" : '.*\.\([0-9a-f]\{64\}\)\.') + got=$(sha256sum "$f" | cut -d' ' -f1) + [ "$want" = "$got" ] || fail "content hash mismatch: $f (actual: $got)" +done + +# --- (b) referenced hashed assets exist on disk ---------------------------- +refs() { + grep -oE '[A-Za-z0-9_/.-]*\.[0-9a-f]{64}\.[A-Za-z0-9.]+' "$1" | sort -u +} + +for src in _header.html errdocs/err.html site.webmanifest; do + [ -f "$src" ] || { fail "expected source file missing: $src"; continue; } + for ref in $(refs "$src"); do + [ -f "${ref#/}" ] || fail "$src references missing asset: $ref" + done +done + +# --- (c) header and errdocs reference identical hash tokens ---------------- +tokens() { + refs "$1" | sed 's|.*/||' | sort -u +} + +header_tokens=$(tokens _header.html) +err_tokens=$(tokens errdocs/err.html) +if [ "$header_tokens" != "$err_tokens" ]; then + fail '_header.html and errdocs/err.html disagree on hashed assets:' + printf '%s\n' '--- _header.html:' "$header_tokens" \ + '--- errdocs/err.html:' "$err_tokens" >&2 +fi + +exit "$status" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86fc170..5d5e6e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,3 +21,12 @@ repos: - id: check-case-conflict - id: check-added-large-files args: [--maxkb=600] + + - repo: local + hooks: + - id: check-asset-hashes + name: check asset hashes + entry: .github/scripts/check-hashes.sh + language: script + pass_filenames: false + always_run: true From 1f9bd458198d503e79a87ecf805a2a1ee12fcf72 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:02:08 -0600 Subject: [PATCH 06/14] chore(tooling): add typos spell checking Report-only: --force-exclude retained from upstream defaults so excludes apply to explicitly passed filenames, --write-changes dropped. First-run false positives (heading-anchor slugs, British 'uncatalogued') handled in config; no real typos found in content. --- .pre-commit-config.yaml | 9 +++++++++ .typos.toml | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .typos.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d5e6e3..415ae8e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,15 @@ repos: - id: check-added-large-files args: [--maxkb=600] + - repo: https://github.com/crate-ci/typos + rev: v1.47.2 + hooks: + - id: typos + # Upstream defaults to --write-changes --force-exclude (autofix). + # Keep --force-exclude (excludes must apply to explicitly passed + # filenames) but drop --write-changes: report-only. + args: [--force-exclude] + - repo: local hooks: - id: check-asset-hashes diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 0000000..ad2c578 --- /dev/null +++ b/.typos.toml @@ -0,0 +1,23 @@ +[files] +extend-exclude = [ + "stagit/", + "migration/", + "pubkeys/", + "fonts/", + "gpg-transition-20250406.txt", + "roAJVp7qzs.txt", +] + +[default] +extend-ignore-re = [ + # hex digests, commit hashes, content-hash filename tokens + "[0-9a-fA-F]{7,}", + # Monero donation address + "8B1s[1-9A-HJ-NP-Za-km-z]{90,}", + # markdown heading-anchor slugs (apostrophes dropped, e.g. "familys") + "\\(#[a-z0-9-]+\\)", +] + +[default.extend-words] +# Grow only from real false positives. Identity mappings accept the word. +uncatalogued = "uncatalogued" From adc7208336a328cc4152c627491c508bd449e898 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:07:39 -0600 Subject: [PATCH 07/14] chore(tooling): add markdownlint Convention rules that conflict with published articles (MD014 prompts, MD029/MD030 list styles, MD036 captions, MD040 bare fences) are disabled in config rather than mass-editing 25 articles. MD041 stays on: every article starts with an H1. --- .markdownlint-cli2.jsonc | 31 +++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 5 +++++ 2 files changed, 36 insertions(+) create mode 100644 .markdownlint-cli2.jsonc diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc new file mode 100644 index 0000000..cfc1634 --- /dev/null +++ b/.markdownlint-cli2.jsonc @@ -0,0 +1,31 @@ +{ + // Auto-exclude gitignored paths (specs/, test/, CLAUDE.md, ...). + "gitignore": true, + "config": { + "default": true, + // No hard line-length limit; prose uses soft wrapping. + "MD013": false, + "MD010": { "code_blocks": false }, + // Shell examples deliberately show "$ " / "# " prompts. + "MD014": false, + "MD024": { "siblings_only": true }, + // Published articles split ordered lists around code blocks and mix + // all-ones with sequential numbering. + "MD029": false, + // Ordered lists legitimately use both 1- and 2-space markers across + // published articles. + "MD030": false, + // Articles use no raw HTML outside code blocks. + "MD033": { "allowed_elements": [] }, + // Emphasis lines are deliberate captions (e.g. "Tested on ..."), + // not headings. + "MD036": false, + // Fenced blocks without a language are fine; the site renderer does + // no syntax highlighting. + "MD040": false, + // Every article starts with an H1 title. + "MD041": true + // MD046 stays at its default (per-file consistent code-block style): + // some articles are all-fenced, others all-indented. + } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 415ae8e..4029457 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,6 +31,11 @@ repos: # filenames) but drop --write-changes: report-only. args: [--force-exclude] + - repo: https://github.com/DavidAnson/markdownlint-cli2 + rev: v0.22.1 + hooks: + - id: markdownlint-cli2 + - repo: local hooks: - id: check-asset-hashes From ff486accc1008332a88a1e093f3d431ea7ea985f Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:07:40 -0600 Subject: [PATCH 08/14] fix(content): normalize code blocks and bare URL flagged by markdownlint domain-sift.md mixed indented and fenced code blocks; convert its three indented blocks to fenced so the article is internally consistent. firefox-keyword-search.md pointed at www.wiktionary.org as bare text; make it a real link. --- domain-sift.md | 62 +++++++++++++++++++++------------------ firefox-keyword-search.md | 3 +- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/domain-sift.md b/domain-sift.md index 2ad50d0..233cbf6 100644 --- a/domain-sift.md +++ b/domain-sift.md @@ -29,26 +29,28 @@ that a Domain Name System (DNS) resolver can block those domains. ## Project structure - |-- Changes - |-- LICENSE - |-- MANIFEST - |-- Makefile.PL - |-- README.md - |-- bin - | `-- domain-sift - |-- lib - | `-- Domain - | |-- Sift - | | |-- Manipulate.pm - | | `-- Match.pm - | `-- Sift.pm - `-- t - |-- 00-load.t - |-- Domain-Sift-Manipulate.t - |-- Domain-Sift-Match.t - |-- manifest.t - |-- pod-coverage.t - `-- pod.t +``` +|-- Changes +|-- LICENSE +|-- MANIFEST +|-- Makefile.PL +|-- README.md +|-- bin +| `-- domain-sift +|-- lib +| `-- Domain +| |-- Sift +| | |-- Manipulate.pm +| | `-- Match.pm +| `-- Sift.pm +`-- t + |-- 00-load.t + |-- Domain-Sift-Manipulate.t + |-- Domain-Sift-Match.t + |-- manifest.t + |-- pod-coverage.t + `-- pod.t +``` ## Installation @@ -58,20 +60,24 @@ following commands inside the source directory. Note that `domain-sift` requires Perl 5.36 or later, since subroutine signatures are no longer experimental in that release. - $ perl Makefile.PL - $ make - $ make test - # make install +``` +$ perl Makefile.PL +$ make +$ make test +# make install +``` ## Documentation After installation, you can read the documentation with `perldoc`. `man` often works as well. - $ perldoc Domain::Sift - $ perldoc Domain::Sift::Match - $ perldoc Domain::Sift::Manipulate - $ perldoc domain-sift +``` +$ perldoc Domain::Sift +$ perldoc Domain::Sift::Match +$ perldoc Domain::Sift::Manipulate +$ perldoc domain-sift +``` ## domain-sift and unwind diff --git a/firefox-keyword-search.md b/firefox-keyword-search.md index 8b1b063..f887ef0 100644 --- a/firefox-keyword-search.md +++ b/firefox-keyword-search.md @@ -36,7 +36,8 @@ for several reasons. Let's use [Wiktionary, the multilingual dictionary](https://www.wiktionary.org/) as an example. -Open up Firefox and navigate to www.wiktionary.org. Right click +Open up Firefox and navigate to +[www.wiktionary.org](https://www.wiktionary.org/). Right click Wiktionary's search bar to pull up a context menu with several entries. Left click the "Add a Keyword for this Search…" entry. From d05dadd1eaf7e15945608a0e907ddda4a40c6510 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:14:08 -0600 Subject: [PATCH 09/14] style(css): apply stylelint autofixes and rehash stylesheets Blank-line conventions and keyword lowercasing (currentcolor, optimizelegibility) from stylelint --fix; behavior-identical since CSS keywords are case-insensitive. Both stylesheets renamed to their new content hashes with references updated in _header.html and errdocs/err.html. --- _header.html | 4 +-- errdocs/err.html | 4 +-- ...15a39c91d1528dce02c29b6804632223237099.css | 2 ++ ...69ee9ac80ebdbda38b900173835d0db6014dda.css | 32 +++++++++++++++---- 4 files changed, 32 insertions(+), 10 deletions(-) rename styles-noscript.db6a2714b9e1b1c6beb69fd489ca559c50af8c5232c0ad7b2667633a711440e9.css => styles-noscript.2e04ce2510f9d98db0a2a153b215a39c91d1528dce02c29b6804632223237099.css (99%) rename styles.48c47559d0a9a2eb9160c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css => styles.2f14b79bea0ae2281afa0cf80069ee9ac80ebdbda38b900173835d0db6014dda.css (98%) diff --git a/_header.html b/_header.html index da1d44d..0df7bd9 100644 --- a/_header.html +++ b/_header.html @@ -6,7 +6,7 @@ diff --git a/errdocs/err.html b/errdocs/err.html index 62aea50..c05a3a7 100644 --- a/errdocs/err.html +++ b/errdocs/err.html @@ -6,7 +6,7 @@ diff --git a/styles-noscript.db6a2714b9e1b1c6beb69fd489ca559c50af8c5232c0ad7b2667633a711440e9.css b/styles-noscript.2e04ce2510f9d98db0a2a153b215a39c91d1528dce02c29b6804632223237099.css similarity index 99% rename from styles-noscript.db6a2714b9e1b1c6beb69fd489ca559c50af8c5232c0ad7b2667633a711440e9.css rename to styles-noscript.2e04ce2510f9d98db0a2a153b215a39c91d1528dce02c29b6804632223237099.css index f953a0e..00d1d49 100644 --- a/styles-noscript.db6a2714b9e1b1c6beb69fd489ca559c50af8c5232c0ad7b2667633a711440e9.css +++ b/styles-noscript.2e04ce2510f9d98db0a2a153b215a39c91d1528dce02c29b6804632223237099.css @@ -1,6 +1,7 @@ body { font-family: "Anthesis Legible Sans", sans-serif; } + h1, h2, h3, @@ -9,6 +10,7 @@ h5, h6 { font-family: "Anthesis Legible Sans", sans-serif; } + code, kbd, pre, diff --git a/styles.48c47559d0a9a2eb9160c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css b/styles.2f14b79bea0ae2281afa0cf80069ee9ac80ebdbda38b900173835d0db6014dda.css similarity index 98% rename from styles.48c47559d0a9a2eb9160c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css rename to styles.2f14b79bea0ae2281afa0cf80069ee9ac80ebdbda38b900173835d0db6014dda.css index e74606a..7ba6339 100644 --- a/styles.48c47559d0a9a2eb9160c204ad49c52e80cac72eff64665e8962d0b342bff4fd.css +++ b/styles.2f14b79bea0ae2281afa0cf80069ee9ac80ebdbda38b900173835d0db6014dda.css @@ -13,16 +13,19 @@ https://openfontlicense.org/ margin: 0; padding: 0; } + *, *::before, *::after { box-sizing: border-box; } + html { -moz-text-size-adjust: none; -webkit-text-size-adjust: none; text-size-adjust: none; } + p, h1, h2, @@ -32,6 +35,7 @@ h5, h6 { overflow-wrap: break-word; } + @supports (text-wrap: balance) { :where(h1, h2, h3, h4, h5, h6, figcaption, blockquote footer, cite, dt):where( :not(body > header *, body > footer *, nav *, menu *, button *) @@ -39,6 +43,7 @@ h6 { text-wrap: balance; } } + @supports (text-wrap: pretty) { :where(p, li, dd, blockquote p):where( :not(body > header *, body > footer *, nav *, menu *, button *) @@ -50,21 +55,27 @@ h6 { .fonts-loaded-1 body { font-family: "Anthesis Legible Sans Faux", sans-serif; } + .fonts-loaded-1 :is(code, kbd, pre, samp, tt, var) { font-family: "Anthesis Legible Mono Faux", monospace; } + .fonts-loaded-1 :is(h1, h2, h3, h4, h5, h6) { font-family: "Anthesis Legible Sans Faux", sans-serif; } + .fonts-loaded-2 body { font-family: "Anthesis Legible Sans", sans-serif; } + .fonts-loaded-2 :is(code, kbd, pre, samp, tt, var) { font-family: "Anthesis Legible Mono", monospace; } + .fonts-loaded-2 :is(h1, h2, h3, h4, h5, h6) { font-family: "Anthesis Legible Sans", sans-serif; } + @font-face { src: url("/fonts/anthesis-legible-sans/anthesis-legible-sans-roman-critical-text.54375ca3dae348f0c57a53aafc73d794efcff78bd00a1b55102111a29f55d862.woff2") format("woff2"); @@ -371,7 +382,7 @@ body { padding-block: 1rem; font-family: var(--font-sans); font-kerning: normal; - text-rendering: optimizeLegibility; + text-rendering: optimizelegibility; line-height: 1.5; max-width: 34rem; background-color: var(--bg-color); @@ -401,6 +412,7 @@ blockquote { ul { list-style-type: circle; } + ul, ol, menu { @@ -421,6 +433,7 @@ ol ul { li + li { margin-top: 0.5em; } + li :is(ul, ol) li:first-of-type { margin-top: 0.25em; } @@ -532,6 +545,7 @@ var { font-size: 0.9em; color: var(--code-block-fg-color); } + pre code, pre kbd, pre samp, @@ -543,12 +557,15 @@ pre var { h1 { font-size: clamp(1.25rem, 1.163rem + 0.435vw, 1.5rem); } + h2 { font-size: 1.25rem; } + h3 { font-size: 1.1rem; } + h4, h5, h6 { @@ -607,21 +624,21 @@ a code { text-decoration-thickness: 0.08em; text-underline-offset: 0.16em; text-decoration-skip-ink: auto; - text-decoration-color: currentColor; + text-decoration-color: currentcolor; } @supports (color: color-mix(in oklch, white, black)) { :where(a[href]) { - text-decoration-color: color-mix(in oklch, currentColor 40%, transparent); + text-decoration-color: color-mix(in oklch, currentcolor 40%, transparent); } } :where(a[href]):hover { - text-decoration-color: currentColor; + text-decoration-color: currentcolor; } :where(a[href]):focus-visible { - text-decoration-color: currentColor; + text-decoration-color: currentcolor; } @media (prefers-reduced-motion: no-preference) { @@ -647,7 +664,7 @@ a:focus, summary:focus, [tabindex="0"]:focus, form :focus { - outline: 3px solid currentColor; + outline: 3px solid currentcolor; } @supports selector(:focus-visible) { @@ -669,13 +686,16 @@ footer { font-size: 0.9375em; color: var(--footer-fg-color); } + footer code { color: var(--footer-fg-color); word-break: break-all; } + footer hr { margin-bottom: 1.25em; } + footer p { margin-bottom: 0; } From 973ed6791e65010d545c6a19c17756359668c4aa Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:14:20 -0600 Subject: [PATCH 10/14] chore(tooling): add stylelint extends stylelint-config-standard with targeted overrides instead of nulls where possible: page-break-* aliases are deliberate print compat, -webkit-/-moz-text-size-adjust has no unprefixed support. The hashed styles.*.css files are source and stay linted; only stagit/ is ignored. no-duplicate-selectors and no-descending-specificity are off: the stylesheet is organized in thematic sections that legitimately repeat selectors. The hook entry computes --config-basedir from the stylelint binary so extends resolves inside pre-commit's isolated node env. --- .pre-commit-config.yaml | 12 ++++++++++++ .stylelintrc.json | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 .stylelintrc.json diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4029457..c3927d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,6 +38,18 @@ repos: - repo: local hooks: + - id: stylelint + name: stylelint + # --config-basedir points at pre-commit's isolated node env (located + # via the stylelint binary) so `extends` in .stylelintrc.json can + # resolve stylelint-config-standard. + entry: sh -c 'exec stylelint --config-basedir "$(dirname "$(dirname "$(command -v stylelint)")")/lib" "$@"' -- + language: node + additional_dependencies: + - stylelint@17.13.0 + - stylelint-config-standard@40.0.0 + files: '\.css$' + exclude: '^stagit/' - id: check-asset-hashes name: check asset hashes entry: .github/scripts/check-hashes.sh diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..e22171f --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,18 @@ +{ + "extends": "stylelint-config-standard", + "ignoreFiles": ["stagit/**"], + "rules": { + "selector-class-pattern": null, + "custom-property-pattern": null, + "no-descending-specificity": null, + "no-duplicate-selectors": null, + "property-no-deprecated": [ + true, + { "ignoreProperties": ["/^page-break-/"] } + ], + "property-no-vendor-prefix": [ + true, + { "ignoreProperties": ["/^(-webkit-|-moz-)text-size-adjust$/"] } + ] + } +} From d857d4e1c4c8ebbe31065327cda888e118775020 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:16:51 -0600 Subject: [PATCH 11/14] chore(tooling): add gitleaks pre-push scanning Local hook (the official one hardcodes --staged) running a full-history scan at push time. Allowlists cover content that is public by design: pubkeys/, migration/, stagit/, the GPG transition statement, PGP public key blocks, and the Monero donation address. Validated against full history: no findings. --- .gitleaks.toml | 20 ++++++++++++++++++++ .pre-commit-config.yaml | 14 ++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .gitleaks.toml diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..8bbc891 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,20 @@ +# gitleaks config for anthes.is. Extends the default ruleset; allowlists +# cover material that is public by design. +[extend] +useDefault = true + +[[allowlists]] +description = "Public keys, archived migration content, and stagit output are published on purpose" +paths = [ + '''^pubkeys/''', + '''^migration/''', + '''^stagit/''', + '''^gpg-transition-20250406\.txt$''', +] + +[[allowlists]] +description = "PGP public key blocks and the Monero donation address are public by design" +regexes = [ + '''-----BEGIN PGP (PUBLIC KEY BLOCK|SIGNATURE)-----''', + '''8B1s[1-9A-HJ-NP-Za-km-z]{90,}''', +] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3927d7..0accd79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,3 +56,17 @@ repos: language: script pass_filenames: false always_run: true + # Local hook rather than the official one: upstream hardcodes + # --staged in its entry, which consumers cannot override, and this + # hook should scan full history at push time instead. + - id: gitleaks + name: gitleaks (full history) + entry: gitleaks git --redact --verbose + language: golang + additional_dependencies: + # gitleaks still declares its Go module path under the old + # zricethezav account name. + - github.com/zricethezav/gitleaks/v8@v8.30.1 + pass_filenames: false + always_run: true + stages: [pre-push] From aaa4b467f1d510d3e3868df061e5e5efc4979537 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:19:10 -0600 Subject: [PATCH 12/14] ci: add quality workflow Two jobs on pull_request and pushes to master: the full pre-commit suite (both stages; gitleaks needs full history, hence fetch-depth 0) with the pre-commit cache keyed on the config hash, and zizmor workflow-security auditing with GH_TOKEN set so the online audits actually run. Top-level permissions are empty, jobs get contents: read, actions are SHA-pinned, and checkout never persists credentials. actionlint joins the pre-commit suite in this same commit; both it and zizmor are clean on this workflow. --- .github/workflows/quality.yml | 48 +++++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 5 ++++ 2 files changed, 53 insertions(+) create mode 100644 .github/workflows/quality.yml diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..23b0964 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,48 @@ +name: quality + +on: + pull_request: + push: + branches: [master] + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + # gitleaks scans full history + fetch-depth: 0 + persist-credentials: false + - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} + - run: pipx install pre-commit==4.6.0 + - run: pre-commit run --all-files --show-diff-on-failure + - run: pre-commit run --all-files --hook-stage pre-push + + workflow-security: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - run: pipx install zizmor==1.25.2 + - run: zizmor --format=github . + env: + # Without a token zizmor silently skips its online audits + # (impostor-commit, known-vulnerable/unpinned-uses). + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0accd79..d2c341d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,11 @@ repos: hooks: - id: markdownlint-cli2 + - repo: https://github.com/rhysd/actionlint + rev: v1.7.12 + hooks: + - id: actionlint + - repo: local hooks: - id: stylelint From e463969f3c7af99c9b18f71fa4923945fb882716 Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:19:38 -0600 Subject: [PATCH 13/14] ci: add dependabot configuration Weekly updates for github-actions and pre-commit ecosystems. enable-beta-ecosystems covers dependabot-core paths that still gate the pre-commit ecosystem despite its GA. Caveat: additional_dependencies pins inside .pre-commit-config.yaml (stylelint, stylelint-config-standard, gitleaks) are invisible to both Dependabot and pre-commit autoupdate; bump them manually quarterly. --- .github/dependabot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..34855d0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +version: 2 + +# The pre-commit ecosystem GA'd in March 2026, but some dependabot-core +# paths still gate it behind this flag; harmless if unneeded. +enable-beta-ecosystems: true + +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + commit-message: + prefix: "chore(deps)" + + - package-ecosystem: pre-commit + directory: / + schedule: + interval: weekly + commit-message: + prefix: "chore(deps)" From 009f55ee24e034645f7a8164c344c54e1630a17d Mon Sep 17 00:00:00 2001 From: Ashlen Date: Fri, 12 Jun 2026 15:25:08 -0600 Subject: [PATCH 14/14] ci: add dependabot cooldown zizmor's dependabot-cooldown audit (online-only, so it surfaced in CI rather than the local pre-push run) requires a cooldown so new releases age before being adopted. Seven days for both ecosystems. --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 34855d0..69dec5e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,6 +9,10 @@ updates: directory: / schedule: interval: weekly + # Let new releases age before adopting them (supply-chain hygiene; + # zizmor's dependabot-cooldown audit enforces this). + cooldown: + default-days: 7 commit-message: prefix: "chore(deps)" @@ -16,5 +20,7 @@ updates: directory: / schedule: interval: weekly + cooldown: + default-days: 7 commit-message: prefix: "chore(deps)"