Brand icons from Simple Icons: a second vendored set, and what TRADEMARKS.md asks of it #654
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#654
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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
assets/icons.txtlists the 60 icons insrc/postulo/static/icons/;scripts/sync-icons.mjs:1-46copies exactly those fromnode_modules/lucide-static(pinned inpackage.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 alabel. CI re-copies and fails on a difference (.forgejo/workflows/ci.yml:361-378);tests/test_icons.py:60-80holds the list to the files and to the templates. flag-icons is the other vendored artwork, withLICENSE.txtbeside it (scripts/sync-flags.mjs).src/,tests/,docs/,assets/andscripts/finds only a comment saying there are none (src/postulo/core/link_services.py:92: “No brand marks, by the rule inTRADEMARKS.md; #301 decides whether that ever changes”).TRADEMARKS.md:64-73lists one mark in the tree (Buy Me a Coffee).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.THIRD-PARTY.md:14-23andtests/test_trademarks.py:36-44(THIRD_PARTY); #579 asks for a test that no licence file sits outside it.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
mainatf09b6010b.What Simple Icons is (verified 2026-10-02)
simple-icons16.33.0, released 2026-09-27; releases are weekly. The package is CC0-1.0 and has no advisory in OSV. 3,463 icons indata/simple-icons.json, each a 24×24 SVG withrole="img", a<title>, nofill(so it takescurrentColor), and the brand's colour only in the data (hex).DISCLAIMER.mdsays 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 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.svgis 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.hexand 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
simple-iconspinned exactly inpackage.json;assets/brands.txtlists slugs;scripts/sync-brands.mjscopies them tosrc/postulo/static/brands/and writesNOTICE.txtfrom 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 andgit statusstep.{% brand "github" %}beside{% icon %}: same trimming,<title>androledropped, decorative unless given alabel, adata-brandattribute. Not{% icon %}, whose selectors and tests readdata-icon.THIRD-PARTY.md; a row and a sentence inTRADEMARKS.mdsaying the plugin-logo rule applies to the core's marks too;src/postulo/static/brandsinTHIRD_PARTYintests/test_trademarks.pywith itsNOTICE.txt.Decisions
currentColorfor 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.pyalready computes contrast from tokens; it would compute it fromhexand fail a brand mark under 3:1.TRADEMARKS.md:114-118promises, “it will be changed”.Prior art
node_modulesonly; nothing is shipped but the listed SVGs. Adopt, as a vendored set.django-simple-iconsis listed in Simple Icons' README): a runtime dependency to ship a dozen SVGs, the objection #8 made to every such package. Reject.{% 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 oftests/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 inNOTICE.txt.tests/test_trademarks.pyregisters the directory and its notice; the contrast test passes for every brand mark in both themes.npm run sync:brandsis clean in CI, and fails when a pinned slug is missing.From the list of improvements of 2026-10-02.