0.3.2: sandboxed card rendering and reliability fixes
Summary
Safer rendering. Cards render with JavaScript off, and the page can only load data: URLs, the brand font host (plus fonts.gstatic.com for Google Fonts) and hosts listed in the new card.allowedHosts (HTTPS only). Post text inserted with {{{raw}}} can't run scripts or send data anywhere while a card renders. Checked by rendering a hostile template: 0.3.0 ran the injected script, this branch doesn't. The example cards render the same as before.
Reliability
- A card that can never render counts towards
maxAttemptsand stops retrying, instead of failing every run. - Mastodon: an unclear failure (timeout, 5xx) is retried only within 50 minutes of the first attempt, while Mastodon still remembers the idempotency key. After that it's recorded as
uncertain. Done with a generic "retry before" deadline on the ledger entry, checked against the publish run's own clock. - Lock: a lock file another process has just created gets a moment to be written, and is taken if it's released meanwhile, instead of being reported as unreadable.
- Webhook replies that claim to be JSON but don't parse are logged as a warning.
Lint and doctor
rules.requireAltmakes missing alt text on image posts an error (it stays a warning by default).postline doctorchecks brand colours against WCAG AA contrast (text, muted and chip text at 4.5:1, accent at 3:1). Both example brands pass.
Docs, changelog and version 0.3.2.
Test plan
-
npm test: 110 pass (new: render allow-list, contrast, requireAlt, render-failure cap, Mastodon window inside and after, lock released while waiting, webhook bad JSON) - Both examples re-rendered with the sandbox; fonts and the custom quote layout look identical
- Hostile template: script blocked on this branch, ran on 0.3.0
- Site builds at 0.3.2
Summary by cubic
Sandboxes card rendering so injected post text can't run scripts or send data, and fixes several reliability and publishing gaps.
- Cards now render with JavaScript disabled and load only from the font host,
data:URLs, and hosts incard.allowedHosts(HTTPS only); a non-HTTPSbrand.font.urlis now a config error. - A card that can never render now counts towards
maxAttemptsand stops retrying instead of failing every publish run. - Mastodon retries an unclear failure only within 50 minutes of the first attempt, while the idempotency key is remembered; the earliest deadline survives later failures and is checked again just before sending. After it, the post is marked
uncertain. - A lock file another process just created is given a moment to be written and is taken if it's released meanwhile, instead of reported as unreadable.
- Webhook replies that claim to be JSON but don't parse are logged as a warning.
rules.requireAltmakes missing alt text on image posts an error;postline doctorchecks brand colours against WCAG AA contrast as the base card draws them, and lists colours it can't read.
An end-to-end run against a real brand fixed the gaps it found:
- Drafts from
postline planlint with warnings instead of errors (a mistyped status stays an error), andrenderskips drafts unless one is named or--draftsis passed. - Token CSS variables keep their own spelling as well as lowercase, so Primer's
var(--fgColor-default)works; two tokens that map to the same CSS variable are now a config error; the renderer warns when card content doesn't fit. - A post that has used up
maxAttemptssays what happened and how to retry; the same finding on several channels is reported once, without repeating channel names.
Docs and changelog updated; version bumped to 0.3.2.
Fixes from an end-to-end trial
postline was run end to end for a real brand: GitHub's changelog, with Primer's published design tokens (unmodified), Mona Sans, a custom layout, four channels (files and a signed webhook for real; LinkedIn and Bluesky without credentials), alerts, and the PR report. It found:
postline plandrafts failedlintwith errors, which would fail the PR check. Declared drafts now get warnings; a mistyped status stays an error.- Token CSS variables were lowercased, so Primer's own
var(--fgColor-default)silently matched nothing in a custom layout. Both spellings are now emitted. renderrendered placeholder drafts into the graphics library. Drafts are now skipped unless named or--drafts.- Long panel text pushed a landscape grid card into its footer with no warning. The renderer now warns when content doesn't fit, and the footer keeps its gap.
- A post that had used up
maxAttemptsmadepublishexit 1 with no output. It now says what happened and how to fix it. - The same lint problem was listed once per channel, and errors repeated the channel name.