Releases: yohimik/crier
Release list
v1.1.1
Fixes
- Make repeating-gradient origins round consistently in packaged Go and TinyGo builds with a pinned, two-line webrender patch. Pixel acceptance thresholds are unchanged; unprepared source builds still use upstream webrender's permitted floating-point behavior. (acac2f0) (by yohimik)
- Add stripped, static TinyGo Linux AMD64 and ARM64 binaries alongside the six standard release binaries, using the published TinyGo v0.43.0-net.2 fork. Installers and normal self-update still select standard binaries; tiny assets are opt-in. (acac2f0) (by yohimik)
- Validate tiny artifacts with full CLI integration, real FFmpeg, TinyGo-built update replacements, TLS refusal/trust, offline rollback and same-source pixel comparisons, retaining compiler and artifact provenance. (acac2f0) (by yohimik)
Authors
- yohimik
Validation and provenance
Post-release CI run 34061068016 passed all eight jobs against exact released source acac2f0eae0d98e42f442c7f4e6a36ccf9bc3665 and verified published binary hashes.
- Native Linux, macOS and Windows, each on AMD64 and ARM64: Dispat, platform-script and released-composite-action installation checks passed. Unix smoke suites passed all seven tests; Windows passed six with the one declared Unix-shell-only announcement test skipped.
- Native Linux AMD64 and ARM64: full Go and TinyGo integration passed 144 top-level tests / 156 passing events per compiler, with zero failures or skips, including real FFmpeg, stamped updates, TLS and rollback.
- All 13 PNG comparisons per architecture passed unchanged thresholds. AMD64 was pixel-exact; ARM64 differed only in four event-card pixels by one channel value.
- Standard source format, vet, lint, workflow/shell checks, generated docs, unit/E2E tests, six-target cross-compilation and coverage passed; combined statement coverage was 90.6%.
The original publication was manual, before this CI validation. That was a process error: future publication must use CI/CD. This metadata correction does not replace the exact release tag, binaries, checksum manifest or original acceptance report. The frozen artifact report predates CI; the linked run supplies the subsequent native-platform evidence.
These are Crier application acceptance results, not proof of general Go networking compatibility. The fork retains broader deadline/cancellation and descriptor limitations; generic emulated AMD64 closed-descriptor tests also had failures. See TinyGo acceptance for scope and historical results.
Optional tiny binaries
| Asset | Bytes |
|---|---|
| crier-tiny-linux-amd64 | 16,447,000 |
| crier-tiny-linux-arm64 | 13,835,840 |
Download the matching asset explicitly and verify it against SHA256SUMS. Tiny binaries are Linux-only and are not the installer's or normal self-update's default.
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.1.1/install.sh | CRIER_VERSION=1.1.1 sh& ([scriptblock]::Create((irm https://raw.githubusercontent.com/yohimik/crier/v1.1.1/install.ps1))) -Version '1.1.1'dispat install yohimik/crierv1.1.0
Features
- publish release media together (7edaff9) (by yohimik)
Fixes
-
templates execute without reflect (daf8778) (by yohimik, Claude Fable 5.1)
crier's templates ran on html/template and text/template, whose executors
reach into values and call functions through reflection. That was the one
thing a TinyGo toolchain could not do for crier: its reflect has no
Type.NumOut and no Value.Call, and text/template needs both before it
executes a line, so a fork-built crier panicked on its first template.internal/template/exec is an executor over text/template/parse's tree with
no reflection in it. crier's data is never an arbitrary struct — a document
is YAML or JSON, so maps, lists and scalars — and its function set is its
own, so the executor walks plain values and dispatches functions through
a typed table. Its tests run the same templates through both executors
and require the same output: the truth rules, the missing-key options,
the comparison functions and their errors, the printing, the variable
scoping, the range order. The real templates — every example and both
announcement layouts — render byte for byte what html/template rendered,
comments aside.What changes for a gc binary is small and written down in the template
documentation: one escaping rule (every value escaped as HTML text) where
html/template had a context machine for URLs, CSS and scripts that a
renderer never needed; template comments kept rather than stripped;
method calls limited to times; join accepting a list from the data
document, which the documentation's own example relied on and
text/template refused; and a version line that names TinyGo when TinyGo
built the binary.Under the fork the black-box suite now passes 106 of its tests where it
passed 47, and renders. What fails is the fork's net, which dials and
does not listen, so the stage server cannot serve; that is the fork's to
close, and until it does the release binaries stay gc's.
Authors
- yohimik
- Claude Fable 5.1
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.1.0/install.sh | CRIER_VERSION=1.1.0 sh:CRIER_VERSION = '1.1.0'
irm https://raw.githubusercontent.com/yohimik/crier/v1.1.0/install.ps1 | iexdispat install yohimik/crierv1.0.0
Breaking Changes
-
finally graduated, thanks for not unsubscribing during dogfooding (a83f31b) (by yohimik)
-
rc train before 1.0.0 (aad80fc) (by yohimik)
-
fail closed on anything unrecognised (b5720f3) (by yohimik, Claude Fable 5)
An unknown command word, an unknown flag anywhere, and a stray positional
argument are all usage errors now, and the unknown-command message lists what
would have worked. Nothing is guessed and nothing passes through: the failure
this prevents is a mistyped --dry-run that publishes for real.Adds
--set key=value, repeatable, which sets any key by its dotted name at
the flag layer. A key that does not exist is refused with the nearest one
suggested, because a flag that silently goes nowhere looks like it worked. -
add init and make version a flag (57ead75) (by yohimik, Claude Fable 5)
crier initwrites a configuration to start from: a short commented file by
default, every option with--full, in yaml, json or toml. The generator moved
into internal/configgen and is now shared with tools/gendocs, so what init
writes and what crier.example.yaml holds come from one walk over the registry
and cannot drift apart.crier versionbecomescrier --version, special-cased ahead of the rule that
sends a leading flag to publish.The system font scan is wrapped in a recover: the parser underneath panics
rather than erroring on a font file it cannot read, and one such file took the
whole program down while rendering something that never needed that font. -
make publish the default command (f615fd6) (by yohimik, Claude Fable 5)
Barecrierrenders and posts, so the everyday flow is to change into a
project directory and run one word. Also silences the lint gate with
reasons rather than blanket exclusions.
Features
-
a graduation dresses up and takes its time (50d2f5d) (by yohimik, Claude Fable 5)
The crossing from rc to stable is the release worth stopping for, and
v1.0.0 crossed it in silence. The card now knows a graduation when
dispat hands it one: the cover carries a stable banner counting the
candidates it took, both captions open with the graduation and the
thanks, and the collected train fills the pages behind it. The
linkedin reel stops squeezing into sixteen seconds: six for the cover,
four a page, with render.video.audio-loop, a new key that repeats the
soundtrack for as long as a slideshow runs. Instagram keeps the same
pages carousel, which on graduation day is the whole train. -
linkedin gets the whole release in one post (06700f3) (by yohimik, Claude Fable 5)
One post was the requirement and LinkedIn's schema forbids the easy
shape: a post takes one video or many images, never both, with
content.multiImage documented images-only. So the pages go inside the
video. The reel holds the cover under the fanfare and then leafs
through the changelog pages in reading order, sixteen seconds for the
whole release, one post carrying all of it. The pages render once and
feed everything: the anthem's frames, the reel's, and the cover both
clips open on. No ffmpeg still means the album, cover first. -
the release pings before it posts (b0eafa0) (by yohimik, Claude Fable 5)
A revoked token used to surface as a skipped announcement at the end
of a fifteen-minute run. Now a ping job stands after the plan: the
previous release's binary asks every announcement platform whether the
credentials still work, read-only, nothing posted, and a dead token
fails the run in its first minute. A repository without the secrets
skips the gate and releases without announcing, as before. -
the release chooses its own look and its own fanfare (6e257ee) (by yohimik, Claude Fable 5)
Audio pools join template pools: render.video.audio-pool lists the
files, the run's seed picks one, and the pick is drawn right after the
layout's so the two travel together. The announcement dogfoods both.
Four public-domain anthems now sit beside the card, two layouts share
every load-bearing rule and differ in everything cosmetic, and
announce.sh derives one seed from the version for every pass, so the
stories wear the face the feed wore and any release can be rendered
again exactly as it went out. -
boosty is the fourteenth platform (ee06f06) (by yohimik, Claude Fable 5)
The first platform without an official API: the contract is what the
web editor itself speaks, pinned from four independent client
libraries and encoded in the test fake, with every host configurable.
A post is uploaded images and draft-style text blocks in one call,
and its audience is the new part: free for everyone, priced for a
one-time purchase, or held to a subscription level. Video stays out
until someone captures the editor uploading one; a guessed endpoint
would leave an unplayable file in a paid post. -
youtube is the thirteenth platform (10784ca) (by yohimik, Claude Fable 5)
Video only, because that is all the Data API takes: community image
posts have no public API at all. OAuth refresh tokens the way reddit
taught crier, a resumable session and one streamed PUT, the caption as
the description and its first line as the title. A Short is not an
endpoint, just a vertical video that says so. Privacy defaults to
private, which is also what Google forces on unaudited projects, and a
refused thumbnail is a warning, because the video is already up. -
threads is the twelfth platform (841b4c5) (by yohimik, Claude Fable 5)
The same Meta machinery Instagram taught crier: a container per page,
a poll to FINISHED, one publish, and the bounded retry for the refusals
proven to have created nothing. Threads adds two rules of its own: a
carousel wants at least two items, so a single page posts as a plain
image, and the token is a Threads token, which an Instagram token is
not. Twenty pages to a carousel, captions ride as the post's text. -
the release announces itself on linkedin too (16a46fd) (by yohimik, Claude Fable 5)
The anthem clip is the announcement: a LinkedIn post takes one video or
many images, never both, so the cover with its soundtrack carries the
commentary and the changelog pages follow as a multi-image album. The
commentary is written around what makes the post worth stopping for:
the changelog came from the commits, and the render and the publish
both happened inside the release run. No secrets, no post, no red
release, as ever. -
vk is the eleventh platform (a5e621e) (by yohimik, Claude Fable 5)
A wall post is three borrowed steps: an upload slot from
photos.getWallUploadServer, the bytes to that slot, and the
server-photo-hash triple handed back through photos.saveWallPhoto.
Pages become one post of up to ten attachments, a clip goes through
video.save, and a GIF travels as a document, which is what VK calls
one. The owner id's sign decides whose wall: negative is a community,
posted as the community itself. -
a twentieth probe to round the parade off at a number worth a page break of its own (0d89c31) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
an eighteenth probe of comfortable two-line length, the kind most real subjects turn out to be (f1ef42a) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a sixteenth probe, generously proportioned in the manner of the eighth, invoking imaginary flags and fictitious platforms for no purpose but the three lines it will be given on the card (8297cdf) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
the release renders its anthem once and leads with it (42ff389) (by yohimik, Claude Fable 5)
The clip was rendered inside the story pass and existed only for that
pass. It is now rendered first, on its own, to a temp file that outlives
the step: crier render with the frames input and the audio, output to
anthem.mp4.Both surfaces then use that one file. The feed pass passes it as
--publish-instagram-lead-video, so the carousel opens with the fanfare
before the cover card. The story pass publishes the same bytes with
--publish-input, so the reel opens with it too and nothing is encoded
twice. Order is unchanged: feed, anthem story, changelog stories.Rendering it f...
v1.0.0-rc.9
Fixes
-
a fitted platform reshapes the file it was handed (453b273) (by yohimik, Claude Fable 5)
rc.8's anthem story was the square anthem.mp4 published with the story
frame's flags. Publish-only mode passed the file through as it arrived,
so the flags did nothing and Instagram padded the square to 9:16 on its
own servers, in black rather than in the card's colour.Passing a file through is right for a format preference and wrong for a
fit. A fit says what shape the platform is to receive; ignoring it hands
the decision to the platform, which crops or pads without saying where
the cut fell. So a platform that set one now gets the file reshaped: an
image is resampled, a clip is re-encoded through the same filter a
rendered clip is fitted with. The audio stream is copied rather than
encoded again, because on this clip the soundtrack is the point.Those modes had one shared variant, on the grounds that overlays and
render sizes are instructions to a renderer that is not running. A fit is
not one of those, so they now group by frame alone: platforms that asked
for nothing still share the file as it arrived, and two that asked for
the same frame share one re-encode.The same gap in the frames-input path is closed with it, since the
machinery was already there and unused: an encoded frames run and its
poster are fitted like a rendered clip, so --render-variant previews the
shape the platform will get. -
number every page of a coverless card (8c9269d) (by yohimik, Claude Fable 5)
The first-page rule that hides the small badge and the page counter is
about page one being the cover, not about page one. The updates document
has no cover, so its first page is the first changelog page, and the rule
stripped its margin: rc.8's two-page story pair was numbered on the
second page and nowhere else, and the first story never said which
release it was.It is now emitted only when there is a cover. A coverless document also
needs a source for the version string, since the only thing that sets it
is the big badge the cover carries, so one is drawn with no height and no
type: the small badge would otherwise be blank on every page rather than
on the first.Both halves have a test. The rendered pages are read back and the two
margin boxes are counted for ink, so a coverless page one has a badge and
a counter, and a cover page one still has neither. -
instagram wants media_type on a video carousel child (e6a7bad) (by yohimik, Claude Fable 5)
rc.8's feed post failed with 400 IGApiException code 100, "The parameter
image_url is required" (fbtrace A0MpoozUBsHxq-KQTcd9gcG). Without a
media_type the API presumes the child is an image and asks for the
parameter an image child would have.The reference lists CAROUSEL, REELS and STORIES for that parameter and
omits VIDEO entirely, which is what the previous change read as an
instruction to leave it out. The endpoint disagrees, and the endpoint is
what posts. Behaviour wins; the docs now say so and cite the trace.Both Instagram fakes refuse a video child that omits it, answering with
the message Meta answered with. Removing the parameter now fails the
suite instead of passing it and failing a release.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.9/install.sh | CRIER_VERSION=1.0.0-rc.9 sh:CRIER_VERSION = '1.0.0-rc.9'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.9/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.8
Features
-
a twentieth probe to round the parade off at a number worth a page break of its own (0d89c31) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
an eighteenth probe of comfortable two-line length, the kind most real subjects turn out to be (f1ef42a) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a sixteenth probe, generously proportioned in the manner of the eighth, invoking imaginary flags and fictitious platforms for no purpose but the three lines it will be given on the card (8297cdf) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
the release renders its anthem once and leads with it (42ff389) (by yohimik, Claude Fable 5)
The clip was rendered inside the story pass and existed only for that
pass. It is now rendered first, on its own, to a temp file that outlives
the step: crier render with the frames input and the audio, output to
anthem.mp4.Both surfaces then use that one file. The feed pass passes it as
--publish-instagram-lead-video, so the carousel opens with the fanfare
before the cover card. The story pass publishes the same bytes with
--publish-input, so the reel opens with it too and nothing is encoded
twice. Order is unchanged: feed, anthem story, changelog stories.Rendering it first is also what makes the feed pass able to lead with it,
which is the point. Without ffmpeg, or when the render fails, anthem.mp4
stays empty: the feed pass goes out with no lead video and the anthem
story is skipped, which is the same thing the missing-ffmpeg path always
did.This also carries the in-flight nocover work the tree was found with: the
data document grows a nocover flag, the template honours it, and the
story pass posts the changelog pages alone because the cover story is now
the video. The two changes touch the same lines of announce.sh and could
not be separated into two commits that both run. -
the carousel and the album open with the clip (331ce8a) (by yohimik, Claude Fable 5)
Instagram builds a video child first and lists it first in the parent.
The child is video_url plus is_carousel_item and nothing else: media_type
names a container kind, and its documented values are CAROUSEL, REELS and
STORIES. VIDEO is no longer among them, and REELS is refused inside a
carousel outright, so the kind is inferred from which URL was sent. Meta
accepts no caption on a child either. Video children process
asynchronously and the existing container wait already covers that, so
the parent is not created until the clip says FINISHED.Telegram prepends an InputMediaVideo to the media group, which is the one
Telegram shape that mixes a clip with photos. The caption belongs to the
album's first item, so it travels with the clip and still appears under
the album.A clip is one of the ten items on both, so a run that opens with one
declares nine pages per post. The audio takes nothing away at Telegram:
it is a message of its own, so an album can carry a lead video and the
track after it. A story ignores the clip and says so, because a story has
no carousel to open and one config file drives both passes. -
a thirteenth probe, medium length, with just enough said to wrap once (1d177a5) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
an eighth probe that describes an imaginary feature in the language of a real one, mentioning a configuration key that does not exist and a platform that never heard of it, purely to occupy three wrapped lines (37db480) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a seventh, short probe (c24eb86) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a sixth probe about nothing at all, phrased the way changelog entries actually arrive, with a clause after the comma that pushes it past the width of one line (89d2baf) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a fifth probe, whose subject settles in at around one hundred and sixty characters so that it reliably wraps to a second line and reaches toward a third on the card (81d9ee7) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a post can name the clip that opens it (7157208) (by yohimik, Claude Fable 5)
publish..lead-video, on the same terms as music-file: the key
exists for all ten platforms, and the eight that cannot post mixed media
are answered by Validate with the reason rather than by the decoder with
an unknown-key error.Two platforms can. An Instagram feed carousel takes video children beside
image children. A Telegram media group mixes InputMediaPhoto and
InputMediaVideo in one array. Everywhere else a post is pictures or it is
a video, and no arrangement of API calls makes it both.There is no shared key to fall back to, unlike the audio. A clip that
opens a post is part of that post's composition rather than a decoration
applied to every surface, and the two that take one want different shapes
of it often enough that naming it twice is the honest spelling.
Fixes
-
a nineteenth probe that wraps twice by design, padding its clause with qualifiers the way release notes do when nobody edits them down before the tag (70a545d) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a seventeenth probe recounting, in the past tense and with unnecessary chronology, the discovery of a bug that never existed and the weekend that was never lost to it (d7daa57) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a fifteenth probe that closes the parade at a length calculated to wrap twice, so the last page of the card has some weight to carry as well (4a13cbf) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a fourteenth probe, one word longer than the tenth (cb715ab) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a twelfth probe whose subject would have been two sentences anywhere else, joined here by a comma because commit subjects have no full stops to spare, and stretched to guarantee its third line (d7a6667) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
an eleventh probe carrying a middling subject that lands almost exactly on the wrap boundary of the card face (e5f1211) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a tenth probe, terse (4fc6440) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card. -
a ninth probe fixing nothing, at length: the sort of subject where the author explains the symptom, the cause and the cure all before the colon has any right to expect them (20a5e0f) (by yohimik, Claude Fable 5)
A dogfooding probe for the paginated release card.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.8/install.sh | CRIER_VERSION=1.0.0-rc.8 sh:CRIER_VERSION = '1.0.0-rc.8'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.8/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.7
Features
-
a fourth probe that stands in for the release notes nobody plans: written in one breath, trailing detail after detail, the kind of subject that a reviewer asks to be split and the author promises to split next time and never does (253da0d) (by yohimik, Claude Fable 5)
-
a probe whose subject describes, at deliberate and unhurried length, the way a paginated release card lets every one of these words survive onto the page where a single-line card would have cut them at the first ellipsis (5acbc6d) (by yohimik, Claude Fable 5)
A dogfooding probe: this subject and its siblings exist so the next
release candidate's card shows long entries wrapping to their full three
lines and pushing one another across page after page.
Fixes
-
a third probe, short (372329b) (by yohimik, Claude Fable 5)
-
a second probe, briefer than the first but still comfortably past the width of one card line, so the wrapped entries alternate with the short ones on the pages (3d18235) (by yohimik, Claude Fable 5)
-
a long entry wraps instead of losing its ending (dd5445e) (by yohimik, Claude Fable 5)
One line per entry was the single-page card's economy. A paginated card
can afford the truth: up to three lines each, then the multi-line clamp,
and an unbroken run breaks anywhere rather than escaping. A long entry
costing three lines is what pushes its neighbours onto the next page,
which is the point of having pages.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.7/install.sh | CRIER_VERSION=1.0.0-rc.7 sh:CRIER_VERSION = '1.0.0-rc.7'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.7/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.6
Fixes
-
Meta's second costume for the same race, and ffmpeg for the runner (3c95025) (by yohimik, Claude Fable 5)
The rc.5 story died on error 24, Media Not Found: the container this
process created and polled FINISHED seconds earlier did not exist at
the publish endpoint for a moment. A container that does not exist
published nothing, so it joins 9007 in the ask-again matcher — and one
genuinely gone spends the budget and surfaces the same error.The anthem skipped for a plainer reason: the runner image carries no
ffmpeg. The release job now installs it when the announce secrets are
present.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.6/install.sh | CRIER_VERSION=1.0.0-rc.6 sh:CRIER_VERSION = '1.0.0-rc.6'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.6/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.5
Features
-
the release opens its story with a fanfare (a5d3b9f) (by yohimik, Claude Fable 5)
A third announce pass: the cover page held for sixteen seconds as a
video story, with sixteen seconds of the 1812 Overture finale as its
soundtrack — a public-domain recording by the United States Marine Band,
provenance in announce/anthem.md. Video is the one way audio reaches
Instagram, which takes no audio file and no track id. The cover renders
once and is copied into frames: 384 identical layouts would cost
minutes, one copied 384 times costs a second. -
ping checks the audio file, and publish says when nothing carries it (79b8393) (by yohimik, Claude Fable 5)
A row per configured music file: the file is there, it can be read, and
its first bytes are one of the four containers crier sends. The extension
is not consulted, because an image renamed to .mp3 is accepted by the
file system and refused by the platform, after the post.The rows come before the publishers are built, since building one refuses
a file that is not audio — a check that ran after the build would never
be reached by the configuration it exists to explain.A file no enabled platform can carry is a success with a note rather than
a failure. The file is fine. Publishing warns about the same finding, so
a run that enables Instagram and X and names a jingle says out loud that
the jingle is going nowhere. -
three platforms carry the audio, each its own way (4668bee) (by yohimik, Claude Fable 5)
Discord and Slack put the track in the same message as the pictures.
Discord takes it as another files[n] part; Slack gives it an upload slot
like any other file, and step three shares them together, which is what
makes them one message.The audio takes one of the message's file slots, so both declare one
fewer page per post when music is configured. Reserving the slot is what
lets a long document paginate into messages that fit, rather than into a
message the platform refuses.Telegram cannot do that. The Bot API groups audio only with audio, so a
track cannot join the album it belongs to. It goes out as its own
message, immediately after, in the same chat, which is what clients
render as a player under the pictures. That message failing is a warning
rather than a failure: the post is already out, and saying the platform
failed would be untrue.TikTok gets auto_add_music in post_info for a photo direct post. It is
the only music setting any of these APIs offers, and it names no track. -
the configuration can name an audio file (96b28ae) (by yohimik, Claude Fable 5)
publish.music-file names one audio file for the whole run, and
publish..music-file overrides it for one platform. Both anchor
to the config file the way every other path key does.The per-platform key exists for all ten platforms rather than only for
the three whose API can carry audio. A key that simply did not exist for
Instagram would answer "can I attach a track here" with an unknown-key
error, which reads like a typo; the key exists, its reference row says it
is not available, and a value in it is refused with the reason. Only
Discord, Slack and Telegram have an endpoint that takes an audio file at
all.publish.tiktok.auto-add-music comes along beside it. It is the one music
setting any of these APIs offers, and it names no track: TikTok picks
one. -
WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW (23fc4a2) (by yohimik, Claude Fable 5)
A deliberate hostile-changelog probe, and nothing else: this subject is
a hundred unbroken copies of the widest glyph the card's face carries,
here so the next release candidate has to ellipsise it on its own card,
in production, where the smoke suite's copy of the same monster already
passes. A changelog is what people wrote; the card has to survive that.
Fixes
-
ask again where a refusal created nothing (5469a24) (by yohimik, Claude Fable 5)
Three platforms share Instagram's race between media processed and media
usable, and each now gets the treatment its own API earns:- Mastodon refuses a status whose attachment is still processing with a
422 raised before any status exists; it is retried within the poll
budget. The message is localized per account, so a non-English
instance falls back to the never-repeat rule, which is the safe
direction to miss in. - X answers a not-yet-consistent media id with the same 400 it uses for
a genuinely wrong one, and neither creates a tweet; the bounded retry
costs a permanent mistake the poll budget and then surfaces it
unchanged. Two new keys carry the budget. - LinkedIn is the dangerous one: the post can be accepted with 201, sit
in PUBLISH_REQUESTED, and end PUBLISH_FAILED with nothing visible and
no error on any call crier made. Images now wait for AVAILABLE before
the post, the way videos always did. - Reddit is the inverse and gets no retry at all: its failure signal can
arrive for a post that was nonetheless created. - Slack blocks until ingestion finishes; TikTok fails inside its own
polled flow; Facebook photos are synchronous. Nothing to do on any of
the three.
- Mastodon refuses a status whose attachment is still processing with a
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.5/install.sh | CRIER_VERSION=1.0.0-rc.5 sh:CRIER_VERSION = '1.0.0-rc.5'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.5/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.4
Fixes
-
green padding between the text and the page margin (13d5a50) (by yohimik, Claude Fable 5)
Type sat flush against the white frame on every page. The panel now
extends past the text, and the cover height subtracts the padding it
gained. -
ask again when Instagram says the media is not ready (8dc5170) (by yohimik, Claude Fable 5)
A container can poll FINISHED while Meta is still making the media
publishable, and media_publish then answers error 9007, media not
available. That refusal means no post was created, so it is the one
publish failure that is safe to retry, bounded by the poll budget. Seen
on the rc.3 release: the carousel published and the first story,
seconds behind it, was told to wait a moment. Every other publish
failure keeps the never-repeat rule.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.4/install.sh | CRIER_VERSION=1.0.0-rc.4 sh:CRIER_VERSION = '1.0.0-rc.4'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.4/install.ps1 | iexdispat install yohimik/crierv1.0.0-rc.3
Features
-
the release announces itself as a carousel (34535f2) (by yohimik, Claude Fable 5)
The card was one page, and a changelog was cut to two or three entries a
section to make it fit. That was the constraint the whole card was designed
around, and it is gone.Page one is the cover: the version badge a thumbnail has to carry, the lede,
and the three install routes pinned to the bottom edge. The changelog carries
on across the pages after it, under a small version badge and a footer that
numbers the page. Both are drawn in the page margins, so they repeat without
being written into the flow, and the first page suppresses them because it
carries its own badge and because "1 / 1" on a short release says nothing.Each pass turns those pages into what its surface takes. The feed post is one
carousel. The story pass posts one story per page, in order, each live before
the next is created. Neither is a flag: crier works it out from the platform.
Stories have no cover-page opt-out — announcing a release without saying what
is in it is not a saving worth making.notes.sh keeps a ceiling of twenty entries a section, which is a guard against
absurdity rather than a design constraint: past it the render would exceed
render.pages-max and be refused outright, and a release with sixty entries in
one section is not one anybody reads to the end of.The end-to-end test now asserts the whole shape against a fake Graph API: a
child container per page, one parent listing them in order, one publish, and
then a story per page. The committed preview is both pages. -
the last three platforms, and where each number came from (1ddab98) (by yohimik, Claude Fable 5)
LinkedIn takes two to twenty as a multi-image post. One image is a different
shape entirely — content.media rather than content.multiImage, and a
multiImage of one is refused — so the shape follows the batch size rather than
a flag. TikTok's photo post was already a list of URLs, so several pages are
more entries in the request it always sent, up to the documented thirty-five.Reddit stays at one file per post on purpose. It has galleries, but the only
way to make one is an endpoint Reddit's own web client uses and Reddit
documents nowhere, with no published limit and no promise it keeps working.
Several ordinary posts that will still be there next month beat one gallery
built on that.The numbers are now sorted into the ones a platform documents and the ones
crier chose. Instagram's ten, Telegram's two-to-ten, x's four, LinkedIn's
twenty and TikTok's thirty-five are the platform's own words. Facebook,
Discord and Slack document the mechanism and no limit on it, so ten is crier's
ceiling rather than theirs, and the comments say so. Mastodon's four is the
instance's, advertised as max_media_attachments and merely defaulted to four,
so crier assumes the conservative number.One test lists every capacity with the source of each, so a number that
changes without that list changing is a post that would silently start
splitting. -
the platforms that take several files now do (21f8ef6) (by yohimik, Claude Fable 5)
Seven of the ten accept more than one file in a post, and each does it
differently. Every one of them keeps the page order.Instagram builds a carousel: a child container per image, a parent that lists
them, one publish. The caption belongs to the parent, which is what makes a
five-page changelog one entry in the feed rather than five. Stories have no
carousel at all, so a story run stays at one file and becomes one story per
page, published in order.Facebook uploads each photo unpublished and attaches the ids to one feed post.
Telegram sends a media group, with the caption on the first item because
Telegram shows an album's caption once; a batch of one still goes through
sendPhoto, since a media group has a minimum of two. x and Mastodon upload in
page order and list the ids. Discord numbers its file parts. Slack uploads each
file and shares the lot in one completeUploadExternal, which is what makes them
one message.A custom platform gets CRIER_ARTIFACTS and CRIER_URLS, one path per line
because a path may contain a space, plus a count and the post and page numbers.
Its capacity is whatever max-attachments says, since the command is the
platform and there is no API here to know better.The e2e suite proves the three shapes against the fakes: sequential stories,
one carousel, and a four-cap split. One scenario runs all three platforms
together and asserts each saw the same five pages in the same order. It is in
the smoke subset, so the release build proves it against the shipped bytes.LinkedIn, Reddit and TikTok still take one file each. They follow.
-
a page list becomes a sequence of posts (e1754f0) (by yohimik, Claude Fable 5)
The run has one ordered page list. Every platform receives it whole, in order,
and the only thing a platform may do to it is cut it into the sizes it accepts.
Nothing reorders, skips, merges or dedupes pages. That is what makes a carousel
at one platform and a run of single posts at another tell the same story in the
same sequence.Needs.MaxAttachments declares how many files a platform takes in one post, with
zero read as one. Every platform still declares one, so today a five-page
document is five posts everywhere; raising the numbers and implementing the
multi-file calls is the next commit. publish..max-attachments lowers
it, and only lowers it: asking a platform for more than it takes would only be
refused by the platform, which is a worse way to find out.A platform's posts go out one at a time, each finished before the next begins.
That is not caution. Several platforms order a feed by when a post completed,
so publishing two of one sequence at once is how a two-part post turns up back
to front. A post that fails stops the ones after it and the outcome says how
far it got: a gap in the middle of a sequence is worse than a short sequence,
because a reader cannot tell it happened. Platforms still run alongside each
other; the sequencing is within one.Captions are rendered once per post rather than once per run, with .Post,
.Posts, .Page and .Pages bound, so one line of configuration can write "2 of
3". They read 1 of 1 when nothing paginated, so a caption that mentions them is
safe to write either way. -
keep every page a document lays out into (8c35002) (by yohimik, Claude Fable 5)
Laying out was always producing pages. crier kept the first and refused the
rest, so a changelog too long for one card was an error telling you to shorten
it.It is a post now. A run produces one ordered page list, and that list is what
every platform receives: same pages, same order, everywhere. The fit is applied
per page, so a carousel is a set of pictures the same shape rather than one
fitted picture and four that were not. Staging gives each page an address of
its own, because a platform that fetches by URL fetches every page.render.pages-max, default 10, refuses a document past it. The check runs after
the layout, since the count is not knowable until the content has flowed: a
template with a loop that never ends fails here rather than at the platform,
which would have taken the first ten and said nothing about the rest. The hard
ceiling is 20, above every platform's own carousel limit bar Reddit's.A document that still fits on one page produces one page with the file name it
always had, so nothing downstream has to learn about pagination to keep
working.crier renderwrites the whole set, numbered when there is more than
one.Nothing publishes more than one page yet. That is the next commit.
Fixes
-
the page margin is the template's to choose (fdd258c) (by yohimik, Claude Fable 5)
crier appended its page rule after the document's own styles, and that rule
carried two declarations: the size and a zero margin. The size belongs there.
Putting it last is what makes --render-width win over a rule the template
happens to carry.The margin did not belong there. Appended last it overruled whatever margin the
template asked for, and a page's margin boxes draw in the page margin. With the
margin forced to zero they had no room: a running header and footer came out
clipped to a sliver, straddling the page edge. A paginated template could not
have worked, which is why this surfaced while verifying paged media at all.The rule is split across the document now. The margin goes first, as a default
a template can overrule. The size still goes last, where it cannot be. A
template that says nothing still gets an edge-to-edge page, because that is
what a social image is.Verified through the raster backend: page margin boxes paint, the page counters
resolve, the first-page selector picks page one, running headers from content
work, and a card that would straddle a page break moves whole. A three-page
golden pins the lot.
Authors
- yohimik
- Claude Fable 5
Install
curl -fsSL https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.3/install.sh | CRIER_VERSION=1.0.0-rc.3 sh:CRIER_VERSION = '1.0.0-rc.3'
irm https://raw.githubusercontent.com/yohimik/crier/v1.0.0-rc.3/install.ps1 | iexdispat install yohimik/crier