Phase 2: Blog automation hardening + Instagram content pipeline#24
Merged
Conversation
…pipeline Phase 2 — content automation. Blog publisher hardening (blog/publish-scheduled.php): - CRITICAL FIX: regenerate_blog_index() previously OVERWROTE the fully designed, SEO-optimized blog/index.html with a bare unstyled <ul> on the first scheduled publish — destroying the page the homepage links to. It now injects styled .blog-card markup between BLOG_POSTS_START/END markers, preserving the page and existing cards. Idempotent (skips already-carded posts) and fail-safe (does nothing if the markers are missing rather than clobbering the page). - Quality gate: pid_is_publishable_post() blocks bare stub drafts (missing site chrome / too little copy) from going live. The overdue placeholder draft that would previously have published-then-nuked the index is now correctly skipped. - Added --dry-run mode to preview what would publish without moving/writing files. - Card metadata sourced from scheduled-posts.json (title/category/description/ date) with a category→image fallback map. Shared helpers (forms/lib/form-helpers.php): - New pure, testable pid_is_publishable_post() and pid_render_blog_card() (HTML-escaped, slug-sanitized). Tests: added pid_is_publishable_post + pid_render_blog_card suites (XSS + path traversal covered). 43/43 pass. Verified index injection end-to-end in an isolated tree: card injected, page integrity preserved (492 lines, GTM/SEO/cards intact — not the old 12-line list), idempotent on re-run, valid HTML. Instagram content pipeline (automation/instagram/): - parse_calendar.py: .xlsx → content-calendar.json, stdlib-only (no pip). Keeps every worksheet; surfaces the calendar sheet as `posts`. - build_queue.py: calendar → queue.json + per-post copy-paste bundles/*.md, with a draft→approved→scheduled→posted status preserved across re-runs. - Schema, labeled sample calendar (pipeline runs before the real parse), and a README documenting how to wire a real poster (Meta Graph API / scheduler). - Generated calendar/queue/bundles are git-ignored; only source is tracked. Note: the authoritative calendar must be generated by re-running parse_calendar.py against the source workbook (the upload is ephemeral and could not be persisted). All linters pass (HTMLHint/Stylelint/ESLint); 43/43 PHP unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2zjTE7NTsrMH1FT6iwdc9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 2 — content automation. Two deliverables: hardening the blog scheduled-publish system (which had a critical page-destroying bug) and scaffolding the Instagram content pipeline from the 30-day calendar workbook.
1. Blog publisher hardening
🛑 Critical fix: index no longer self-destructs
regenerate_blog_index()previously overwrote the fully designed, SEO-optimizedblog/index.htmlwith a bare unstyled<ul>on the first scheduled publish — wiping the page the homepage blog teaser links to. It now injects styled.blog-cardmarkup betweenBLOG_POSTS_START/BLOG_POSTS_ENDmarkers, preserving the designed page and existing cards.This was not hypothetical: the overdue placeholder draft (
kitchen-paint-selection-guide, due 2026-07-01) would have triggered exactly this destruction on the next cron run.Quality gate
New
pid_is_publishable_post()blocks bare stub drafts (missing site chrome / too little copy) from going live. That overdue stub is now correctly skipped instead of published.Dry-run
php blog/publish-scheduled.php --dry-runpreviews what would publish without moving or writing anything.Verification (isolated tree)
Published a styled draft end-to-end → card injected, page integrity preserved (492 lines, GTM/SEO/existing cards intact — not the old 12-line list), idempotent on re-run, injected markup passes HTMLHint.
2. Instagram content pipeline (
automation/instagram/)Integration-agnostic scaffolding — prepares and tracks content; does not post (that needs Meta Graph API or a scheduler; documented in the README).
parse_calendar.py—.xlsx→ structured JSON, stdlib-only (no pip). Preserves every worksheet.build_queue.py— calendar → status-tracked queue + per-post copy-paste bundles. Status (draft→approved→scheduled→posted) is preserved across re-runs.Testing
pid_is_publishable_post+pid_render_blog_cardsuites, incl. XSS + path-traversal escaping)Action required
The authoritative
content-calendar.jsonmust be generated by re-runningparse_calendar.pyagainst the source workbook — the upload is ephemeral and could not be persisted, and the calendar can't be faithfully reconstructed from a partial capture without inventing content. Re-attach the.xlsxand it's one command to populate the full 30-day calendar.🤖 Generated with Claude Code
https://claude.ai/code/session_01R2zjTE7NTsrMH1FT6iwdc9
Generated by Claude Code