Brand icons from Simple Icons: a second vendored set, and what TRADEMARKS.md asks of it #654

Closed
opened 2026-10-03 01:58:36 +00:00 by tiagoagueda · 0 comments
Owner

The maintainer's list says: “Add Simple Icons (https://github.com/simple-icons/simple-icons)”. Postulo has one icon set, Lucide, which has no brand marks (#8), so a service such as GitHub or ORCID is drawn with a generic icon or none. #301 left the question open (its decision 3: “Icons for brands … a brand mark only where its owner's terms allow it”), and #305 built on the generic icons and deferred to it.

What exists today

  • Lucide only. assets/icons.txt lists the 60 icons in src/postulo/static/icons/; scripts/sync-icons.mjs:1-46 copies exactly those from node_modules/lucide-static (pinned in package.json) and exits non-zero on a name Lucide lacks; {% icon %} (src/postulo/core/templatetags/postulo.py:44-90) inlines the file, strips the root's size and class, and is decorative unless given a label. CI re-copies and fails on a difference (.forgejo/workflows/ci.yml:361-378); tests/test_icons.py:60-80 holds the list to the files and to the templates. flag-icons is the other vendored artwork, with LICENSE.txt beside it (scripts/sync-flags.mjs).
  • No brand mark anywhere. A search for Simple Icons or a brand mark in src/, tests/, docs/, assets/ and scripts/ finds only a comment saying there are none (src/postulo/core/link_services.py:92: “No brand marks, by the rule in TRADEMARKS.md; #301 decides whether that ever changes”). TRADEMARKS.md:64-73 lists one mark in the tree (Buy Me a Coffee).
  • A rule exists, written for plugins. TRADEMARKS.md:91-108: a plugin may ship someone else's logo when it identifies something the plugin works with, is unmodified (“not recoloured to match a theme, not redrawn, not composed into something else”), carries a notice naming the owner and claiming no endorsement, and sits in its own directory outside the licence grant. Where one fails, the neutral fallback is shown. The reasoning holds for a mark in the core's own interface; the text does not say so.
  • The register is checked against a fixed list. THIRD-PARTY.md:14-23 and tests/test_trademarks.py:36-44 (THIRD_PARTY); #579 asks for a test that no licence file sits outside it.
  • Where a mark would go first. #305 landed the web-link services: 15 of them (src/postulo/plugins/link_services/services.py:36-50: LinkedIn, Mastodon, Bluesky, X, Xing, Facebook, Instagram, Threads, YouTube, GitHub, GitLab, Codeberg, Forgejo, Bitbucket, SourceHut), each naming a Lucide icon (LinkService.icon, core/link_services.py:123), with the kind's generic icon as the fallback (:93, :189); the plugin says “no service has a mark” (services.py:20). Identifier schemes (core/identifiers.py, plugins/identifiers/schemes.py) have no icon field; #301 names ORCID, and #672 would use one. Instant-messaging services (#682) and job boards (plugins/builtin/boards/) are further cases.

Checked against main at f09b6010b.

What Simple Icons is (verified 2026-10-02)

  • npm simple-icons 16.33.0, released 2026-09-27; releases are weekly. The package is CC0-1.0 and has no advisory in OSV. 3,463 icons in data/simple-icons.json, each a 24×24 SVG with role="img", a <title>, no fill (so it takes currentColor), and the brand's colour only in the data (hex).
  • Its DISCLAIMER.md says CC0 “doesn't mean to imply that all icons within the project are also CC0”, and asks users to “seek the correct permissions”. 223 icons carry their own licence entry (Apache-2.0 49, MIT 29, CC-BY-SA-4.0 29, custom 23, CC-BY-4.0 19, CC0-1.0 11, and others); 836 link the owner's brand guidelines. Of the services above, Codeberg and SourceHut are CC0-1.0 and Forgejo is CC-BY-SA-4.0.
  • Removals follow owners' terms. Brands write to removals at simpleicons dot org, or anyone may open a removal issue; icons are removed “twice a year in our major releases”. LinkedIn was removed this way: linkedin.svg is in 13.21.0 and absent from 14.0.0 (2024-12-22) and 16.33.0, after issue 11372 of that project, opened by a user and not by LinkedIn, quoted LinkedIn's guidelines that it “generally does not permit” third parties to use its logos. A maintainer answered that Microsoft's legal team had already had most Microsoft icons removed (LinkedIn was not on that list) and that the terms settled it. Slack, Microsoft Teams and Skype are absent too; X stands for Twitter. Fourteen of the fifteen services above have a mark; LinkedIn, the one a job search names most, is the one that does not.
  • ORCID is present, and its display guidelines say “The icons must be used as provided, and must not be changed or altered in any way”, with the green icon used “in most circumstances”, a high-contrast black-and-white one allowed where accessibility needs it, a reversed-out white one for darker backgrounds, and no other colourway.
  • The published colours do not all survive a theme. Computed on 2026-10-02 from each mark's hex and the page tokens (white; ink-950, oklch(0.13 0.01 250)): against the dark page, X, Threads, SourceHut (000000) and GitHub (181717) are 1.0 to 1.1:1, Xing and Bitbucket 2.9:1; against white, ORCID is 1.8:1, Forgejo 2.3:1 and GitLab 2.9:1. Not yet a test.

Proposal

  1. Vendor the set the way Lucide is. simple-icons pinned exactly in package.json; assets/brands.txt lists slugs; scripts/sync-brands.mjs copies them to src/postulo/static/brands/ and writes NOTICE.txt from the package's data (owner, source, guidelines link, licence per mark). It exits non-zero on a slug the package no longer has, so an upstream removal is a decision at the bump and not a silent change. CI gets the same re-copy and git status step.
  2. {% brand "github" %} beside {% icon %}: same trimming, <title> and role dropped, decorative unless given a label, a data-brand attribute. Not {% icon %}, whose selectors and tests read data-icon.
  3. Register it. A row in THIRD-PARTY.md; a row and a sentence in TRADEMARKS.md saying the plugin-logo rule applies to the core's marks too; src/postulo/static/brands in THIRD_PARTY in tests/test_trademarks.py with its NOTICE.txt.
  4. Let a service or a scheme name a mark, with its Lucide icon as the fallback, which is the rule's own “neutral fallback”. Each mark is recorded with how it is drawn (Decisions). First candidates: the fourteen services above and, with an icon field on the scheme, ORCID and Wikidata. Each only where it identifies something Postulo works with.

Decisions

  • Which kinds get a mark. Recommended: web-link services and identifier schemes now; job boards and instant messaging when their issues land. Never decoration.
  • LinkedIn. Options: the generic icon; a mark from another source; none. Recommended: the generic icon, and no mark taken from elsewhere, since Simple Icons removed it on the strength of LinkedIn's own terms.
  • Recolouring. Rule 2 says unmodified, and the numbers above say the published colour fails on one theme or the other for nine of sixteen marks. Options: published colour only (nine marks drop to the generic icon); currentColor for all (a recolouring everywhere); published colour on a tile; a recorded mode per mark. Recommended: a recorded mode per mark in the notice: brand (the published colour, only where it clears 3:1 on both surfaces), single colour (currentColor, only where the owner's guidelines allow it, with the link recorded; ORCID's own text allows a black-and-white version for accessibility and a white one on a dark background, and no other colour), or none, which is the generic icon. This narrows rule 2 and needs the maintainer's yes; without it, the nine take the generic icon. tests/test_contrast.py already computes contrast from tokens; it would compute it from hex and fail a brand mark under 3:1.
  • Per-icon licences. Forgejo's CC-BY-SA-4.0 asks for attribution and share-alike on the artwork. Recommended: the sync script copies each licence into the notice and refuses one outside the list #653 sets.
  • A mark withdrawn upstream. The pinned copy keeps shipping. Recommended: a bump is a deliberate commit that reads the removals; an owner who writes to the project is answered as TRADEMARKS.md:114-118 promises, “it will be changed”.

Prior art

  • Simple Icons: above. CC0-1.0, weekly releases, no advisory, 16 MB unpacked in node_modules only; nothing is shipped but the listed SVGs. Adopt, as a vendored set.
  • Font Awesome Free brands (609 brand icons; CC BY 4.0, attribution): weighed in #8, where the freemium catalogue steering to Pro counted against it. Not re-verified here. Reject.
  • A PyPI wrapper (django-simple-icons is listed in Simple Icons' README): a runtime dependency to ship a dozen SVGs, the objection #8 made to every such package. Reject.
  • Hand-drawn marks: no; the rule's second condition forbids redrawing.
  • The project hand-writes {% icon %} (about thirty lines, #8). It is not a standard and nothing is gained by a library for it.

Builds on / Needed by

Builds on #8, #107, #279, #301, #305, #579 and #653. Needed by #672, #678 and #682 where they want a mark.

Done when

  • tests/test_brands.py (new), in the shape of tests/test_icons.py: the committed marks are exactly the listed slugs; {% brand %} is decorative by default and a template error for an unknown slug or a path; every mark has an owner line and a mode in NOTICE.txt.
  • tests/test_trademarks.py registers the directory and its notice; the contrast test passes for every brand mark in both themes.
  • npm run sync:brands is clean in CI, and fails when a pinned slug is missing.
  • No record type: the archive format (29 today) and the candidate format (4) are untouched. New strings only if a mark needs a name for a screen reader, in en-GB, fr-FR and pt-PT (drafts) and Weblate (#349).

From the list of improvements of 2026-10-02.

The maintainer's list says: “Add Simple Icons (https://github.com/simple-icons/simple-icons)”. Postulo has one icon set, Lucide, which has no brand marks (#8), so a service such as GitHub or ORCID is drawn with a generic icon or none. #301 left the question open (its decision 3: “Icons for brands … a brand mark only where its owner's terms allow it”), and #305 built on the generic icons and deferred to it. ## What exists today - **Lucide only.** `assets/icons.txt` lists the 60 icons in `src/postulo/static/icons/`; `scripts/sync-icons.mjs:1-46` copies exactly those from `node_modules/lucide-static` (pinned in `package.json`) and exits non-zero on a name Lucide lacks; `{% icon %}` (`src/postulo/core/templatetags/postulo.py:44-90`) inlines the file, strips the root's size and class, and is decorative unless given a `label`. CI re-copies and fails on a difference (`.forgejo/workflows/ci.yml:361-378`); `tests/test_icons.py:60-80` holds the list to the files and to the templates. flag-icons is the other vendored artwork, with `LICENSE.txt` beside it (`scripts/sync-flags.mjs`). - **No brand mark anywhere.** A search for Simple Icons or a brand mark in `src/`, `tests/`, `docs/`, `assets/` and `scripts/` finds only a comment saying there are none (`src/postulo/core/link_services.py:92`: “No brand marks, by the rule in ``TRADEMARKS.md``; #301 decides whether that ever changes”). `TRADEMARKS.md:64-73` lists one mark in the tree (Buy Me a Coffee). - **A rule exists, written for plugins.** `TRADEMARKS.md:91-108`: a plugin may ship someone else's logo when it identifies something the plugin works with, **is unmodified** (“not recoloured to match a theme, not redrawn, not composed into something else”), carries a notice naming the owner and claiming no endorsement, and sits in its own directory outside the licence grant. Where one fails, the neutral fallback is shown. The reasoning holds for a mark in the core's own interface; the text does not say so. - **The register is checked against a fixed list.** `THIRD-PARTY.md:14-23` and `tests/test_trademarks.py:36-44` (`THIRD_PARTY`); #579 asks for a test that no licence file sits outside it. - **Where a mark would go first.** #305 landed the web-link services: 15 of them (`src/postulo/plugins/link_services/services.py:36-50`: LinkedIn, Mastodon, Bluesky, X, Xing, Facebook, Instagram, Threads, YouTube, GitHub, GitLab, Codeberg, Forgejo, Bitbucket, SourceHut), each naming a Lucide icon (`LinkService.icon`, `core/link_services.py:123`), with the kind's generic icon as the fallback (`:93`, `:189`); the plugin says “no service has a mark” (`services.py:20`). Identifier schemes (`core/identifiers.py`, `plugins/identifiers/schemes.py`) have no icon field; #301 names ORCID, and #672 would use one. Instant-messaging services (#682) and job boards (`plugins/builtin/boards/`) are further cases. Checked against `main` at `f09b6010b`. ## What Simple Icons is (verified 2026-10-02) - npm `simple-icons` 16.33.0, released 2026-09-27; releases are weekly. The package is CC0-1.0 and has no advisory in OSV. 3,463 icons in `data/simple-icons.json`, each a 24×24 SVG with `role="img"`, a `<title>`, no `fill` (so it takes `currentColor`), and the brand's colour only in the data (`hex`). - Its `DISCLAIMER.md` says CC0 “doesn't mean to imply that all icons within the project are also CC0”, and asks users to “seek the correct permissions”. 223 icons carry their own licence entry (Apache-2.0 49, MIT 29, CC-BY-SA-4.0 29, custom 23, CC-BY-4.0 19, CC0-1.0 11, and others); 836 link the owner's brand guidelines. Of the services above, Codeberg and SourceHut are CC0-1.0 and Forgejo is CC-BY-SA-4.0. - **Removals follow owners' terms.** Brands write to `removals at simpleicons dot org`, or anyone may open a removal issue; icons are removed “twice a year in our major releases”. **LinkedIn was removed this way**: `linkedin.svg` is in 13.21.0 and absent from 14.0.0 (2024-12-22) and 16.33.0, after [issue 11372 of that project](https://github.com/simple-icons/simple-icons/issues/11372), opened by a user and not by LinkedIn, quoted LinkedIn's guidelines that it “generally does not permit” third parties to use its logos. A maintainer answered that Microsoft's legal team had already had most Microsoft icons removed (LinkedIn was not on that list) and that the terms settled it. Slack, Microsoft Teams and Skype are absent too; X stands for Twitter. **Fourteen of the fifteen services above have a mark; LinkedIn, the one a job search names most, is the one that does not.** - ORCID is present, and its display guidelines say “The icons must be used as provided, and must not be changed or altered in any way”, with the green icon used “in most circumstances”, a high-contrast black-and-white one allowed where accessibility needs it, a reversed-out white one for darker backgrounds, and no other colourway. - **The published colours do not all survive a theme.** Computed on 2026-10-02 from each mark's `hex` and the page tokens (white; `ink-950`, `oklch(0.13 0.01 250)`): against the dark page, X, Threads, SourceHut (`000000`) and GitHub (`181717`) are 1.0 to 1.1:1, Xing and Bitbucket 2.9:1; against white, ORCID is 1.8:1, Forgejo 2.3:1 and GitLab 2.9:1. Not yet a test. ## Proposal 1. **Vendor the set the way Lucide is.** `simple-icons` pinned exactly in `package.json`; `assets/brands.txt` lists slugs; `scripts/sync-brands.mjs` copies them to `src/postulo/static/brands/` and writes `NOTICE.txt` from the package's data (owner, source, guidelines link, licence per mark). It exits non-zero on a slug the package no longer has, so an upstream removal is a decision at the bump and not a silent change. CI gets the same re-copy and `git status` step. 2. **`{% brand "github" %}`** beside `{% icon %}`: same trimming, `<title>` and `role` dropped, decorative unless given a `label`, a `data-brand` attribute. Not `{% icon %}`, whose selectors and tests read `data-icon`. 3. **Register it.** A row in `THIRD-PARTY.md`; a row and a sentence in `TRADEMARKS.md` saying the plugin-logo rule applies to the core's marks too; `src/postulo/static/brands` in `THIRD_PARTY` in `tests/test_trademarks.py` with its `NOTICE.txt`. 4. **Let a service or a scheme name a mark,** with its Lucide icon as the fallback, which is the rule's own “neutral fallback”. Each mark is recorded with how it is drawn (Decisions). First candidates: the fourteen services above and, with an icon field on the scheme, ORCID and Wikidata. Each only where it identifies something Postulo works with. ## Decisions - **Which kinds get a mark.** **Recommended:** web-link services and identifier schemes now; job boards and instant messaging when their issues land. Never decoration. - **LinkedIn.** Options: the generic icon; a mark from another source; none. **Recommended:** the generic icon, and no mark taken from elsewhere, since Simple Icons removed it on the strength of LinkedIn's own terms. - **Recolouring.** Rule 2 says unmodified, and the numbers above say the published colour fails on one theme or the other for nine of sixteen marks. Options: published colour only (nine marks drop to the generic icon); `currentColor` for all (a recolouring everywhere); published colour on a tile; a recorded mode per mark. **Recommended:** a recorded mode per mark in the notice: *brand* (the published colour, only where it clears 3:1 on both surfaces), *single colour* (`currentColor`, only where the owner's guidelines allow it, with the link recorded; ORCID's own text allows a black-and-white version for accessibility and a white one on a dark background, and no other colour), or none, which is the generic icon. This narrows rule 2 and needs the maintainer's yes; without it, the nine take the generic icon. `tests/test_contrast.py` already computes contrast from tokens; it would compute it from `hex` and fail a *brand* mark under 3:1. - **Per-icon licences.** Forgejo's CC-BY-SA-4.0 asks for attribution and share-alike on the artwork. **Recommended:** the sync script copies each licence into the notice and refuses one outside the list #653 sets. - **A mark withdrawn upstream.** The pinned copy keeps shipping. **Recommended:** a bump is a deliberate commit that reads the removals; an owner who writes to the project is answered as `TRADEMARKS.md:114-118` promises, “it will be changed”. ## Prior art - **Simple Icons**: above. CC0-1.0, weekly releases, no advisory, 16 MB unpacked in `node_modules` only; nothing is shipped but the listed SVGs. **Adopt, as a vendored set.** - **Font Awesome Free brands** (609 brand icons; CC BY 4.0, attribution): weighed in #8, where the freemium catalogue steering to Pro counted against it. Not re-verified here. **Reject.** - **A PyPI wrapper** (`django-simple-icons` is listed in Simple Icons' README): a runtime dependency to ship a dozen SVGs, the objection #8 made to every such package. **Reject.** - **Hand-drawn marks:** no; the rule's second condition forbids redrawing. - The project hand-writes `{% icon %}` (about thirty lines, #8). It is not a standard and nothing is gained by a library for it. ## Builds on / Needed by Builds on #8, #107, #279, #301, #305, #579 and #653. Needed by #672, #678 and #682 where they want a mark. ## Done when - `tests/test_brands.py` (new), in the shape of `tests/test_icons.py`: the committed marks are exactly the listed slugs; `{% brand %}` is decorative by default and a template error for an unknown slug or a path; every mark has an owner line and a mode in `NOTICE.txt`. - `tests/test_trademarks.py` registers the directory and its notice; the contrast test passes for every *brand* mark in both themes. - `npm run sync:brands` is clean in CI, and fails when a pinned slug is missing. - No record type: the archive format (29 today) and the candidate format (4) are untouched. New strings only if a mark needs a name for a screen reader, in en-GB, fr-FR and pt-PT (drafts) and Weblate (#349). From the list of improvements of 2026-10-02.
tiagoagueda added this to the 0.6.0 milestone 2026-10-03 01:58:36 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#654
No description provided.