fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
feat: implement logging.file and configurable decision types, rebuild the docs (#94)
Phase 2 and 3 of the documentation plan, plus three items requested
directly. Every gate here was mutation-tested, because three of them
shipped failing open and were only caught that way.
logging.file is implemented. It was parsed and read by nothing; all
output went to stderr. Lines now go to BOTH stderr and the file, so
journalctl and docker logs keep working — the file is an addition, not a
redirection. Append, mode 0640 (log lines record banned clients' IP
addresses), parent directories deliberately not created, and a failure
to open is a startup error naming the path rather than a silent fallback.
A log file you configured and did not get is the failure class the
previous PR spent its time removing from the docs.
crowdsec.supported_decisions_types does something now. It was declared,
defaulted, mapped to an environment variable and read by nothing while
parseDecision hardcoded "ban". CrowdSec's decision type is a free string,
so a scenario emitting a custom type now has a way to be enforced. The
design follows what the Local API actually does, measured rather than
assumed: `type` matches exactly (type=ban,throttle returns zero) and
omitting it returns every type, so the default set keeps its server-side
filter and only a widened set fetches everything and filters locally.
The configuration reference is generated from the Go struct: a committed
artefact the docs render, regenerated and diff-checked in CI, retiring
110 hand-typed Env/Default lines across both locales. It immediately
caught six keys publishing defaults they do not have — per-protocol
rule-placement overrides inherit from the global placement rather than
from the constructor.
The token layer replaces a 681-line stylesheet with seven single-concern
sheets. Dark on bare :root, light on :root[data-theme="light"], every
colour token restated in both. The heading ladder is monotonic at every
viewport width now; it used to render h3 smaller than h4, and the first
fix left h5 and h6 below the body copy they introduce.
The landing is rebuilt from one HomeContent interface both locales must
satisfy, with the FAQ structured data generated from the same array as
the visible answers. Its new "What it writes to your router" section
publishes the limits on the front page, including a CPU figure measured
on the production router — a transient peaking at 29-34% against a 7%
baseline for about six seconds, in 11 of 11 cycles — together with the
warning that SNMP monitoring will not show it, because hrProcessorLoad
reports a one-minute average that flattens the spike to roughly 9%.
The firewall rules are single-sourced from internal/manager and rendered
on both the reference page and the landing. This corrected a
long-standing undercount: a stock configuration writes EIGHT rules, not
four. The two passthrough counting rules were absent from every listing
on the site, and they are written even when metrics.enabled is false.
Verified byte-for-byte against a production RB5009.
Three gates were failing open and now are not. The contrast gate's
symmetry check compared resolved palettes, so it was structurally blind
to a colour declared on bare :root and never restated for light. The
schema extractor still produced 93 keys after every SetDefault call was
deleted. The i18n gate could not see a section vanishing from one locale
when its heading stayed behind, so it now compares component
invocations.
Off-site documentation links use the canonical jmrp.io address the
repository homepage advertises; the Astro site keeps the Pages address it
is generated for.
Thirteen review threads addressed. Two CodeQL alerts in the gate scripts
themselves, both about hand-rolled markdown parsing, are fixed — that
file is scheduled to move onto the MDX AST.
Claude-Session: https://claude.ai/code/session_01ENguejGi5gZcxoMbCKWFBy
fix(docs): the retired favicon, a broken home-page card, and a 1.61:1 print border — each with the gate that missed it (#96)
Closes the loose ends left over from the phase-5 work, plus the two
workstreams
that were still outstanding (W13 per-page OG cards, W14 icon/manifest
hygiene).
Three of the five things fixed here were **shipped defects nobody could
have
caught**, because in each case the pipeline had no check that joined the
two
halves involved. Each is now closed by a gate as well as by the fix.
### `favicon.ico` was the retired shield
Every icon is rendered from `logo-light.svg` — except the `.ico`, which
was last
written by the *July* logo redesign and carried the
shield-with-WiFi-arcs
through the whole mark change. It is the one asset the author never
looks at,
and the one Google's SERP fetcher and older Safari prefer.
Now packed from the same PNGs as everything else, at 16/32/48. Written
by hand:
sharp cannot encode ICO and an ImageMagick dependency would not survive
CI. The
container is a 6-byte header and one 16-byte entry per image. 15,086
bytes → 553.
### The home page's social card 404'd
The card URL is assembled in two places holding different ids for the
same page
— the endpoint reads the content collection, which calls the English
home page
`index`; the `Head` override sees Starlight's route, whose id for it is
empty.
They agreed on 55 pages and disagreed on the one most links point at,
which
shipped as `og/.png`. Starlight also synthesises a 404 route with an
entry and
no collection page behind it, so that page pointed at a card nothing
rendered.
Both now resolve through one `cardPath`, and membership in the
collection — not
the presence of an entry — decides whether a page has a card.
**`check-social-cards.mjs`
reads every `og:image` out of the rendered HTML and asks the filesystem
whether
it is there.** It found the 404 defect within a second of being written.
### The print stylesheet shipped a 1.61:1 boundary
`@media print` was exempt from the contrast gate because "paper is one
background and the palette does not reach it". The first half is true;
the
second does not follow. Browsers do not print background colours by
default, so
on paper the `pre` border is the only thing separating a code block from
the
prose around it — a graphical object required to understand the content,
so
1.4.11 applies. `#ccc` → `#8a8a8a`, 1.61:1 → 3.45:1.
### Also
- **A social card per page** (W13) — 56 pages shared one banner. Text is
set in
the mono face the site already uses, which makes wrapping exact
arithmetic
rather than a guess, since librsvg does not measure text.
- **The i18n gate invented one mismatch and missed two** — `path="a.b"`
vs
`path={"a.b"}` keyed apart (the self-test asserted this, so the bug was
pinned
by its own suite); `<Home section />` and `<Home />` keyed identically;
a stray
`<Foo-Bar />` was reported as `Foo`.
- **Manifest and browser chrome follow the palette** (W14) — three
`#0e1316`
literals matched the token by coincidence. `theme-color` is also now
split in
two, since one dark value painted a dark address bar above a white page
for
every light-theme reader. New maskable icon: without one Android does
not crop
to the launcher shape, it shrinks the mark onto a plain white tile.
### Verification
Every fix is mutation-tested — reverting it makes exactly one named
check fail.
The brand rasters are byte-identical after the `brand-assets.mjs`
refactor,
which is the evidence that separating logic from IO changed no output.
Also checked, and closed with no change needed: the `picomatch` lockfile
churn
(`--frozen-lockfile` is in sync), the 753 KB `grafana-dashboard.png`
(referenced
from the JSON-LD, not stray), and CrowdSec's brand terms — they publish
no
trademark policy, their MIT carries no trademark clause, and this repo
ships no
CrowdSec or MikroTik logo. The exposure is the name used descriptively,
which is
what every third-party bouncer on their Hub does.
https://claude.ai/code/session_01Lt5tP3miz9YCv21qWsjBUo
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/96?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
<div id='description'>
<a href="https://bito.ai#summarystart"></a><h3>Summary by
Bito</h3><ul><li>Implemented per-page social cards generated at build
time, replacing the single shared image.</li>
<li>Updated browser theme colors and web manifest to dynamically follow
the site's color palette, improving accessibility and OS
integration.</li>
<li>Refactored the landing page to use a typed content contract,
ensuring consistency between human-readable text and machine-readable
structured data.</li>
<li>Fixed multiple documentation inaccuracies regarding binary behavior,
CLI paths, and configuration defaults by aligning them with the Go
source code.</li>
<li>Corrected broken binary download links and installation snippets by
dynamically resolving the latest release tag and fixing architecture
suffixes.</li>
</ul></div>
## Summary by Sourcery
Close remaining documentation and branding inconsistencies by generating
page-specific social assets, aligning browser metadata and content with
the product, and adding gates for the defects that previously escaped
validation.
New Features:
- Generate a distinct social card for each documentation page with
localized page titles and section labels.
- Add maskable app-icon support and synchronize browser and manifest
colors with the site palette.
Bug Fixes:
- Regenerate the legacy favicon from the current brand assets.
- Fix homepage and invalid-route social-card references so every
declared card resolves to a built file.
- Improve internationalization parity reporting for equivalent string
attributes, valueless attributes, and hyphenated component names.
- Correct print contrast for code-block borders and update inaccurate
documentation, installation commands, and download links.
Enhancements:
- Share brand and page-card content logic across raster generation and
build-time social-card rendering.
- Align architecture diagrams and landing-page content with the
documented and implemented product behavior.
CI:
- Add a build verification gate that checks all rendered social-image
references resolve to files.
Documentation:
- Refresh the changelog and user-facing documentation to reflect current
binary behavior, configuration, installation, and supported
functionality.
Tests:
- Strengthen contrast, manifest, internationalization, brand-asset, and
social-card validation, including mutation-oriented regression coverage.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
- **New Features**
- Added unique social cards for documentation pages, including localized
titles and branding.
- Added a maskable application icon and updated theme colors for light
and dark modes.
- Improved site metadata and branding across shared pages.
- **Bug Fixes**
- Fixed favicon generation and social-card routing.
- Improved print contrast for code blocks and links.
- Corrected internationalization component matching, including
hyphenated names.
- **Documentation**
- Documented social cards, architecture, firewall rules, logging,
configuration references, and updated branding.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
feat(docs): read the Person entity from its canonical source (#75)
Part of a rollout across the six sites that publish the
`https://jmrp.io/#person` entity. jmrp.io now serves it as a standalone
document; this site consumes it instead of restating it.
## Problem
This site restated the entity by **copying its values**. Across the five
project sites doing the same, that convention failed twice:
- `cs-routeros-bouncer` published *"Open-source developer; author and
maintainer of cs-routeros-bouncer"* as the `description` of the shared
entity — a project description on a person node.
- `Cloudflare-DNS-Updater` advertised an avatar URL that **returned
404** — a fingerprinted Astro asset the portfolio had long since
rebuilt.
Both are fixed upstream. Values that must not diverge should not be
copied at all.
## Change
The node is fetched at build time from the canonical document and
spliced verbatim, so this document and jmrp.io describe the same entity
with the same values **by construction**.
```
raw.githubusercontent.com/jmrplens/jmrp.io/main/public/identity/person.jsonld
```
Fetched from GitHub rather than `https://jmrp.io` **on purpose**: this
build runs on a CI runner, and jmrp.io sits behind Cloudflare, CrowdSec
and a MikroTik bouncer — the one place a blocked runner IP would
silently degrade this site to a stale snapshot. GitHub serves the same
bytes and is already a hard dependency of the build (the checkout comes
from it). If GitHub is down there is no build anyway, which makes the
committed snapshot a belt-and-braces fallback rather than a real
dependency.
## What it gains
Beyond removing the drift risk, this site now asserts things it never
did:
| | Before | After |
| --- | --- | --- |
| `alternateName` | `"jmrplens"` | **8 observed variants**, including
`José M. Requena-Plens` — the citation form in every published paper |
| `sameAs` | 6–10 | **14**, including two forge accounts with signed
OpenPGP proofs |
| `owns` | absent | **11 projects** |
## Deliberate choices
- **`knowsAbout` stays local and is merged, not replaced.** It is
multi-valued, so the project's own topics reinforce the canonical list
instead of conflicting with it. The full local set is kept — not just
the entries the canonical lacks — so this site keeps asserting its own
topics even if that list changes.
- **`creator` + `maintainer` added alongside `author`** on the software
nodes, matching the three agents `jmrp.io/projects/` emits for those
`@id`s. They merge, so a partial set per document was untidy rather than
wrong.
- **Visible footer link to the project index**, localized where the site
is bilingual. Deliberately **not** `rel="me"`: it is a page, not an
identity profile.
- **`pnpm run identity:sync`** refreshes the snapshot deliberately, in
its own commit, so the identity this site would ship without a network
is visible in review rather than frozen at whatever it was the day the
file was added.
## Verification
Built and re-parsed out of the built HTML:
```
props identical to canonical : YES
extra props : none
alternateName 8 · owns 11 · sameAs 14
knowsAbout: canonical 19 + this project's own, no duplicates
```
The full JSON-LD node inventory was compared before and after — not just
the `Person` — after an earlier attempt in this rollout silently deleted
two sibling nodes while still producing a green build.
https://claude.ai/code/session_01Ecf6hESxYdc7f1UV1vHrQU
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/jmrplens/cs-routeros-bouncer/pull/75?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->