{% set page_title = 'Links' %} {% include '_header.html' %} {#- The shared toolbox (ADR-0030). Not documentation and not a runbook — a subject's runbook lives on the check that watches it — but the two or three dashboards and tools somebody mid-incident wants *now*, without hunting through a wiki. So: a launcher. One flat level of groups flowed into columns, each entry a mark and a title, curated order preserved exactly as written. Everything on it comes from the deployment's own `config/links.yaml`; this file owns only how it looks. -#} {%- macro icon_mark(link) -%} {#- A brand logo does not theme: a black mark vanishes on the dark themes and a white one on the light. So an icon that declared a **pair** emits both files and CSS hides one — the choice cannot be made here, because under the `auto` theme the server does not know what the OS will resolve to. Anything else — one file, a favicon — rides a **theme-invariant light chip**, which is what keeps a single-file mark legible in every theme, and one image serves both backgrounds: a second would be the same bytes fetched twice. The chip is the floor, the pair is the upgrade (ADR-0030 §4). Which images, and where each is from, is `LinksConfig.mark_for`'s answer, because the page's own Content-Security-Policy is taken from the same one: an image with a `url` is fetched from elsewhere — a remote mark, or the entry's site favicon — and one without is ours, by the entry's name. -#} {%- set mark = links_config.mark_for(link) -%} {%- for image in mark.images -%} {%- endfor -%} {%- endmacro -%} {#- The setup guide, folded (ADR-0030). With no links the page used to be the guide in full — a sample file, where it goes, the roster of installed marks, how to add one — more than a screen before anybody had a link; and a deployment that had written its file lost the roster with the empty state. So the guide is two folds, each the browser's own disclosure and closed, which works without script: *Add links* on a page that has none, and *Icon names* there and at the foot of a page that has links. A fold is a card, whose surface Bootstrap paints. What its summary adds beside its name is muted from our own token for that reason: `.text-muted` is an ink nobody here measured on that surface (`tests/test_contrast.py`). -#} {%- macro fold(name, aside) -%} {%- endmacro -%} {#- The roster and how a deployment adds a mark of its own. It stands alone under a page that has links, so it names the file it means rather than pointing at a fold that is not there. -#} {%- macro icon_names_fold(icon_names) -%} {%- call fold('Icon names', (icon_names | length) ~ ' installed, and how to add your own') %} {%- if icon_names %}

icon: takes a name, never a URL — a dozen entries for one tool cost one mark, one fetch, one probe. These names are already installed. Each is drawn twice, once for a light background and once for a dark one, and the page picks by theme:

{#- The whole roster, not an example of one: the reader is choosing a name, and a name they cannot see is one they will write wrong. The list is the **registry** (built-in pack plus anything a dependent package registered), so every row here is servable by the icon route — the deployment's own map is not in it, since an entry there may be a remote URL or a suppression, neither of which has bytes to serve. The images are decorative: the name they belong to is the row header beside them, so `alt` is empty rather than a second reading of it. A file that fails to load still becomes the neutral glyph — links.js delegates on `.ls-links__mark`, which is why each one keeps that wrapper here too. -#}
{%- for name in icon_names %} {%- for variant, side in (('on-light', 'light'), ('on-dark', 'dark')) %} {%- endfor %} {%- endfor %}
{%- endif %}

To use a mark of your own, put an icons/ directory beside links.yaml and drop the file in: firewall.svg is the name firewall. Name it after one of the installed marks and it replaces that one. A pair — firewall-on-light.svg and firewall-on-dark.svg — is one name with a drawing for each background; a single file rides a light chip instead, so it stays legible under every theme.

{%- endcall %} {%- endmacro -%} {#- The last step of the search path. An icon that fails to load is replaced by this, so the page never shows a browser's broken-image glyph — the failure the reference implementation was showing three of. It is a template rather than an `onerror` attribute because links.js wires the swap: an inline handler would be the one reason a Content-Security-Policy could not be turned on later. It draws in `currentColor`, so it needs neither a chip nor a pair. -#} {% include '_page_end.html' %}