Twelve repos back at parity — and the release the marketplace couldn't see
metafetch 0.1.7 was tagged, released, and attested. Obsidian's directory still read 0.1.6, because it reads the default branch and the default branch was six commits behind. Every repo in the family is now development = main = master.
Why Care?
You can do everything right on a release and still ship it into a void.
metafetch 0.1.7 was tagged, built in a clean runner, signed with artifact
attestations, and published with hand-written release notes. All correct. And
Obsidian's Community Plugins directory still showed 0.1.6 — because the
directory reads manifest.json at the HEAD of a repo's default branch, that
repo's default branch is master, and master was six commits behind
development.
Nothing was broken. No build failed, no check went red. The release simply wasn't where anyone was looking.
That turned out to be the whole family's shape: every repo's main and master
sat 1–6 commits behind development, and content-farm's sat 15 behind. All
twelve are now at parity, and master serves 0.1.7.
What's New?
Twelve repos at
development = main = master. All clean fast-forwards —aheadwas 0 everywhere, so nothing merged, nothing forced, no history rewritten.Release narratives reach the splash for the first time. They used to live in a root-level
release-notes/folder that nothing rolled up.Eleven submodule pointers bumped, including two vendored upstreams.
The Stenographer share imagery is on the CDN, with its URLs written down rather than left in a terminal scrollback.
The branch tiers were lying
development → main → master is the model, and the tiers had quietly stopped
meaning anything:
| Repo | main/master behind development |
| content-farm | 15 |
| metafetch | 6 |
| cite-wide, image-gin, perplexed | 4 |
| file-transporter, filestarter, grab-reference, lmstud-yo, plunk-it | 2 |
| stenographer, image-wrangler | 1 |
Two details made this safe rather than exciting. Every branch was strictly
behind — ahead was 0 on all of them, so each promotion was a fast-forward
with nothing to reconcile. And the order was submodules first, parent last:
promote the parent first and its main points at submodule commits that only
exist on those submodules' development branches. The SHAs still resolve, but
the tiers would be claiming something was promoted when it wasn't.
Worth knowing that the default branch is not uniform across the family, and it decides what the outside world reads:
| Default branch | Repos |
master | cite-wide, image-gin, metafetch, file-transporter, filestarter, grab-reference, lmstud-yo, plunk-it |
main | perplexed |
development | stenographer, image-wrangler |
Release narratives were invisible to the splash
Each plugin kept two documents per release: a frontmattered narrative at
changelog/releases/<version>.md for the site, and a frontmatter-free copy at
release-notes/<version>.md that only the release workflow read. Deliberately —
perplexed's release page literally closed by linking to "the full
marketing-grade release narrative with deeper context."
The rollup only walks changelog/, so anything in release-notes/ was never
seen. Consolidating to one file per release fixed that as a side effect: the
narratives are now inside changelog/, and the workflow strips the frontmatter
on its way to GitHub instead of us maintaining a second copy.
Merging perplexed's three duplicate pairs had to be done by hand — neither copy was a superset. The release page carried the install steps, the asset verification and the plugin-family table; the narrative carried the deeper per-feature sections and "What's next". Both halves survive.
On the splash side, changelogSchema now declares three fields that arrive with
these documents. date_last_updated was the one that mattered: release docs
carry it, the schema didn't declare it, so it came through .passthrough() as a
raw string — precisely the hazard the comment above the editorial date pair
warns about, where a date chain hands a string to .toISOString(). category
and release_tag are declared so a releases-only filter can rely on them
instead of fishing through passthrough data.
The /public path that answers 200 and renders nothing
The Stenographer share images went to ImageKit, and the reference file recording their URLs exists because of a trap worth stating plainly:
*.jpg is Git LFS in this repo, and GitHub Pages serves the LFS pointer file
rather than the image. A site-relative /public path answers 200 with
content-type: image/jpeg and about 131 bytes of text. It looks healthy to a
link checker and renders as nothing to a human.
That is the entire reason this imagery is CDN-hosted. It isn't a preference, and
it was only recorded in a comment inside one frontmatter block — now it's in
splash/assets/ogimages-imagekit-paths.md alongside all twelve in-use URLs.
Alt text is written for Stenographer's six, after looking at each one; they turn out to be six distinct compositions rather than crops of a single render. Perplexed's six are marked as still needing alt rather than being given plausible-sounding text — alt written without seeing the image satisfies a linter while telling a screen-reader user nothing.
Under the hood
A frontmatter audit ran across the whole tree — eleven plugin repos plus the
parent, +289/-0 lines. Purely additive: site_uuid and hex_code minted on
documents that had no stable identity, plus the editorial date pair where it was
missing. No deletions, no reordering, no body text touched, per the
context-vigilance drift policy.
rollup-sync now reports the split — wrote N files (X release narratives, Y
dated entries) — so a run that silently stops pulling releases is visible rather
than buried in a total. No path change was needed; the Content API walk was
already recursive and already preserved the subpath.
The vendored submodules turned out to be three different shapes, which is worth writing down because "just pull them" is wrong for two of the three:
| Module | Points at | Access | Update path |
obsidian-textgenerator-plugin | upstream directly | READ | plain git pull |
obsidian-git | our fork of Vinzent03/… | ADMIN | gh repo sync per branch |
obsidian-task-manager | our fork of antoneheyward/… | ADMIN | already level |
Pulling a fork reaches our copy, not the parent — which is why obsidian-git
reported "already up to date" while sitting 185 commits behind upstream. Its
master and main synced cleanly; development carried one Lossless commit
(adding the plugin's own generated obsidian_askpass.sh to .gitignore) that a
--force sync would have discarded, so upstream was merged in instead.
That ignore rule is doing real work, incidentally: the plugin generates the file
and tries to self-exclude via .git/info/exclude, which doesn't survive being a
submodule. Upstream has no such entry, so it's a one-line PR waiting to be filed.
What's Next
/llms.txt and /llms-full.txt are already live and already index release
narratives — 8 of them at last check, from perplexed, image-gin and cite-wide.
Metafetch's two join on the next pnpm rollup:sync.
Still open: a way to highlight releases in the splash feed and filter the
changelog down to just releases. The data layer is ready for both — every release
document carries category: Release and a typed release_tag. The open question
is whether releases should keep appearing inline among the dated ship notes, or
get a surface of their own.
