feat(blocks): credit — "Powered by / Created by / Managed by" attribution - #73
Conversation
…tion Catalog 53 → 54. Nothing in the catalog could credit the maker of a site. `copyright` asserts ownership (© year holder) — a different claim from "this was built by X", and the only way to express the latter was to bend `copyright`'s rights text into a line that cannot carry a link at all. One brick, not one per phrasing: `label` is free text, so "Created by", "Managed by", "Built by" and "A project of" all fit the same contract without a new field or a new type. `name`, an optional `logo` (image URL or a single emoji) and a trailing `note` fill the rest, across bar / badge / inline variants. The link is the whole point of the brick, so it is the part that is guarded. `href` runs through sanitizeUrl and a hostile scheme degrades to unlinked plain text rather than href="#" — a credit with no destination should read as text, not as a link that goes nowhere. Outbound means newTab defaults to true, which brings rel="noopener noreferrer" and a visually hidden "(opens in a new tab)": the ↗ glyph is aria-hidden and announces nothing, so without it a screen-reader user is moved to a new tab unwarned. newTab:false drops the target and the warning together, so the promise can never drift from the behaviour. Total render: blank href → unlinked, blank everything → the landmark still renders, and both name and note are escaped. Demoed on the wall via SUPPLEMENT rather than a starter placement — templates stay consumer-neutral, and a credit is only honest when it names someone real, so the wall credits this package's own maker. Drive-by: the README block table still said 52 and was missing `progress` from 0.11.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖 wrokin code reviewAdds credit block for attribution with comprehensive tests; one defensive coding improvement recommended. High-signal issues
Model: deepseek/deepseek-v4-pro · your key, your model (BYOK) |
🤖 wrokin security auditNo security-relevant findings in this diff. All user-supplied text is properly escaped ( Model: deepseek/deepseek-v4-pro · your key, your model (BYOK) |
|
Thanks — checked the one finding; it's a false positive, so no change.
It also can't be reached with an absent The behaviour the finding is aimed at is covered by tests: a missing |
Catalog 53 → 54, additive and non-breaking: every 0.11.x manifest still validates and renders identically. - `credit` (#73) — "Powered by / Created by / Managed by <name>" with an outbound link. Free-text `label` so any phrasing fits one contract; optional logo (image URL or emoji) and note; bar / badge / inline. `sanitizeUrl` on the href with a hostile scheme degrading to plain text, and `newTab` (default) carrying rel="noopener noreferrer" plus a visually hidden "(opens in a new tab)" the ↗ glyph cannot convey. Version stamped into the emitted catalog in the same commit — 0.11.1 needed a follow-up because it wasn't. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Catalog 53 → 54. Adds
credit— the one-line attribution every generated site wants at the very bottom, linking out to whoever built or runs it.copyrightasserts ownership (© year holder). That is a different claim from "this was built by X", and the only way to express the latter today was to bendcopyright's rights text into a line that cannot carry a link at all. The two compose: a copyright bar above a credit line.The block
One brick rather than one per phrasing —
labelis free text, so"Created by","Managed by","Built by","A project of"all fit the same contract with no new field and no new type.label"Powered by")name*hrefnewTabtruelogonote·variantbar(default) ·badge·inlinealignstart·center·endWhy the link is the guarded part
It is the whole point of the brick, so it gets the scrutiny:
hrefruns throughsanitizeUrl, and a hostile scheme degrades to unlinked plain text rather thanhref="#"— a credit with no destination should read as text, not as a link that goes nowhere.newTabdefaults totrue, which bringsrel="noopener noreferrer"and a visually hidden "(opens in a new tab)". The ↗ glyph isaria-hiddenand announces nothing, so without that text a screen-reader user is moved to a new tab unwarned.newTab: falsedrops the target and the warning together, so the promise can never drift from the behaviour.Total render, as required by the brick DoD: blank
href→ unlinked; blank everything → the landmark still renders;nameandnoteare escaped.Samples
{ "type": "credit", "config": { "label": "Created by", "name": "BytesBrains", "href": "https://bytesbrains.com", "newTab": true, "variant": "bar", "align": "center" } }{ "type": "credit", "config": { "label": "Created by", "name": "AiToolK.it", "href": "https://aitoolk.it", "newTab": true, "logo": "🛠️", "note": "Built in minutes, not weeks", "variant": "badge", "align": "center" } }Both, plus the
addBlockop form and when to reach forcreditvscopyright, are documented in a new Crediting the maker section inAGENT.md.Notes
SUPPLEMENT, not a starter placement — templates stay consumer-neutral, and a credit is only honest when it names someone real, so the wall credits this package's own maker.src/credit.test.tscover the new-tab contract, scheme neutralization, escaping, logo URL-vs-emoji, and the empty-config landmark. Full suite: 189 passing.catalog.json/CATALOG.mdregenerated.package.jsonbump —CHANGELOG.mdcarries the0.12.0section for a later release PR.progressfrom 0.11.0.🤖 Generated with Claude Code