Corrective Action Report: Presentation Content Drift from Canonical Source
Date: April 10, 2026 Category: Content Architecture / Partial Migration Impact: 10 manifesto presentation pages displayed commitment titles and body text that did not match the canonical Sanity CMS article content; visitors seeing presentations received different information than visitors reading the published articles Resolution Time: Same session as discovery (April 8-9, 2026, during FR-9 surface refactoring)
Incident
What Happened
During the Unified Canonical Architecture project (FR-9), Chris Carolan identified that the 10 manifesto presentation pages (/presentations/manifesto-*) contained commitment slide content that was completely different from the published manifesto articles in Sanity CMS. The commitment titles, body text, and action items in the presentations had been written independently as creative artifacts when the presentations were first built. When the canonical methodology content was later published as articles and stored in Sanity, the presentations were never updated to source from it.
FR-9's initial pass migrated only the CONFIG data (title, subtitle, navigation metadata) from a local methodology.ts file to Sanity queries, but left the slide body content as hardcoded inline HTML in each .astro file. This created a state where the page metadata came from Sanity but the actual content visitors interacted with did not.
Timeline
| Time | Event |
|---|---|
| Original build | 10 manifesto presentations created as standalone creative artifacts with ~80 lines of hardcoded HTML each |
| Later | Manifesto articles published in Sanity CMS with canonical commitment data (titles, action items) |
| Apr 8 | FR-9 surface refactoring begins; config data (title, subtitle, nav) migrated from local methodology.ts to Sanity queries |
| Apr 8 | V proposes accepting the hardcoded content and monitoring for drift via Showcase |
| Apr 8 | Chris identifies this as the wrong approach -- presentations must render from the same canonical data as articles |
| Apr 8-9 | ManifestoPresentation layout refactored to accept commitments prop; all 10 pages reduced from ~80 lines to 17 lines; 762 lines of hardcoded HTML deleted |
Root Cause
Three failures compounded:
Failure 1: Scope Definition Was Too Narrow
FR-9 was titled "surface refactoring" and scoped as an import swap -- replace local methodology.ts config imports with Sanity queries. The scope doc listed "40 files need refactoring" but only counted the config import changes (title, subtitle, slideCount, navigation metadata). The inline slide body content -- the actual substance visitors see -- was not in the count and not in the scope.
The phrase "surface refactoring" itself contributed to the problem. It framed the work as mechanical find-and-replace rather than "ensure all methodology content flows from the canonical source."
Failure 2: Partial Migration Created False Completion Signal
After FR-9's initial pass, each manifesto page had a Sanity query at the top fetching config data. This looked complete -- the file had a getManifestoPresentation() call, the layout consumed config props, the page rendered. But the slides between the title and closing were still hardcoded HTML with content that did not match Sanity.
Mirror's visual QA passed because it verified that the layout rendered correctly, navigation worked, and slides advanced. Mirror does not compare rendered text against canonical source data. The QA verified structure, not content accuracy.
Failure 3: V Proposed Accepting Drift Instead of Fixing It
When the content mismatch was identified, V initially proposed keeping the hardcoded content and adding a Showcase monitoring check for future drift. Chris correctly identified this as wrong: the presentations must render from canonical data, not be monitored for divergence from it. This is the "monitor instead of fix" anti-pattern -- a variant of the deferral rationalization ("This is a bigger lift -- maybe we do it later").
Fix Applied
ManifestoPresentation Layout Refactored
The layout (src/layouts/ManifestoPresentation.astro) was modified to accept an optional commitments prop:
interface Props {
config: ManifestoConfig;
commitments?: Array<{
number: number;
title: string;
actions: string[];
}>;
}
When commitments is provided, the layout dynamically generates slides 2 through N+1 from the Sanity data. The <slot /> fallback remains for backward compatibility but is no longer used by any manifesto page.
All 10 Manifesto Pages Simplified
Each manifesto page was reduced from ~80 lines (hardcoded slides) to 17 lines (Sanity query + layout invocation with intro/closing slots). The pages now fetch both config and full article data from Sanity:
const config = await getManifestoPresentation('ai');
const fullData = await getManifestoByPresentationSlug('ai');
const commitments = fullData?.commitments || [];
Code Changes
| File | Change |
|---|---|
src/layouts/ManifestoPresentation.astro |
Added commitments prop, dynamic slide generation from Sanity data |
src/lib/sanity/methodology.ts |
Added getManifestoByPresentationSlug() query |
src/pages/presentations/manifesto-*.astro (10 files) |
Replaced ~80 lines of hardcoded HTML with 17-line Sanity-driven pattern |
Verification
Post-fix, each manifesto presentation renders commitment slides directly from the same Sanity data that powers the published articles. Content updates in Sanity automatically propagate to presentations on the next build.
Remaining Exposure
The same pattern exists in two other presentation families that were NOT addressed in this fix:
| Family | File Count | Lines Per File | Total Hardcoded Lines | Content Source |
|---|---|---|---|---|
Trap presentations (trap-*.astro) |
10 | ~131 each | ~1,312 | 7 slides of hardcoded HTML per file (origins, impact, friction, costs, pattern, alternative, breaking free) |
Stage presentations (stage-*.astro) |
8 | ~187 each | ~1,496 | 7 slides of hardcoded HTML per file (what's happening, recognition, org levels, progression, flags, approach, success) |
Combined: 18 files, approximately 2,808 lines of hardcoded slide content that may or may not match their corresponding Sanity canonical data.
These presentations should be evaluated for the same dynamic rendering pattern applied to manifestos. The TrapPresentation and StagePresentation layouts would need the same refactoring: accept a structured data prop, generate slides dynamically from Sanity queries, reduce each page file to the minimal Sanity-query-plus-layout pattern.
Prevention Measures
Architectural Prevention (Already in Place for Manifestos)
The ManifestoPresentation layout now generates slides from data. There is no hardcoded content to drift. Any content update in Sanity automatically propagates to presentations on the next build.
Rules to Add
| Layer | Rule |
|---|---|
| Scope definition | When a refactoring task touches files that source data from both a canonical system AND hardcoded inline content, the scope MUST include migrating the inline content to the canonical source. "Surface refactoring" that leaves inline content intact is a partial migration, not a completed refactoring. |
| QA (Mirror) | Visual QA must include a content accuracy check for pages that are supposed to render canonical data. Verifying that slides render and navigate is not sufficient -- the text on those slides must match the source of truth. |
| Anti-pattern | Proposing to "monitor for drift" on content that should be sourced from a single canonical system is the wrong approach. If the canonical source exists, render from it. Monitoring is for detecting unexpected divergence, not for managing known divergence that should not exist. |
Detection Trigger
If V or any agent encounters a presentation or page that contains hardcoded methodology text (trap descriptions, stage descriptions, manifesto commitments, core belief statements) AND a corresponding canonical source exists in Sanity CMS: the content MUST be migrated to render from Sanity. Do not propose monitoring, do not propose "evaluating later." The canonical source is the source of truth. Render from it.
Lessons
1. "Surface refactoring" is a misleading scope name
The word "surface" implies the work is shallow -- swap imports, update references, done. But methodology presentations are content artifacts. Their substance IS the slide text, not the import at the top of the file. A refactoring that changes how metadata is loaded but leaves the actual content hardcoded has changed the surface while missing the substance.
2. Partial migrations are worse than no migration
Before FR-9, the manifesto files were honestly standalone -- they sourced everything locally. After FR-9's initial pass, they sourced config from Sanity but content from inline HTML. This half-migrated state is worse because it looks complete. The Sanity query at the top of each file signals "this page is canonical" when in fact only the metadata is.
3. QA must verify content accuracy, not just rendering correctness
Mirror verified that the presentations rendered, navigated, and displayed correctly. All true. But "displays correctly" and "displays the right content" are different assertions. For pages that render canonical data, QA needs a content accuracy dimension: does the rendered text match the source of truth?
4. The monitor-instead-of-fix instinct is a deferral pattern
V's initial proposal to accept the hardcoded content and add Showcase monitoring was a variant of "let's start simple and iterate." The reality: when a canonical source exists and a rendering surface does not use it, the fix is to connect them, not to watch them diverge.
Related Incidents
- Paragon account data drift (Mar 10): Silent accumulation of data mismatches across HubSpot, portal, and config.yaml. Same root cause class: multiple sources of truth for the same information, with no mechanism forcing synchronization.
- HubSpot Beginners Course integrity failure (Mar 12): Properties in TypeScript types did not match actual HubSpot properties. Same verification gap: reading the code looked correct, but the code did not match the destination system.
- Portal Documents invisible (Mar 2):
listing_content_typeset to wrong value, document invisible in portal. Same partial-completion pattern: the record was created (looked done) but a critical attribute was wrong (actually invisible).