From 57af0b8386198fea719d53c69800da797141d31a Mon Sep 17 00:00:00 2001 From: Johannes Gasser Date: Mon, 18 May 2026 08:39:42 +0200 Subject: [PATCH] add skills --- .../SKILL.md | 39 ++ .../references/cms-integration.md | 223 ++++++ .../references/common-pitfalls.md | 200 ++++++ .../references/experiment-design.md | 109 +++ .../references/statistical-foundations.md | 150 +++++ .../content-modeling-best-practices/SKILL.md | 32 + .../references/content-reuse.md | 134 ++++ .../references/reference-vs-embedding.md | 89 +++ .../references/separation-of-concerns.md | 60 ++ .../references/taxonomy-classification.md | 136 ++++ .../skills/portable-text-conversion/SKILL.md | 65 ++ .../rules/html-to-pt.md | 242 +++++++ .../rules/manual-construction.md | 210 ++++++ .../rules/markdown-to-pt.md | 204 ++++++ .../portable-text-serialization/SKILL.md | 111 +++ .../rules/astro.md | 124 ++++ .../portable-text-serialization/rules/html.md | 115 ++++ .../rules/markdown.md | 123 ++++ .../rules/plain-text.md | 66 ++ .../rules/react.md | 142 ++++ .../rules/svelte.md | 134 ++++ .../portable-text-serialization/rules/vue.md | 125 ++++ .agents/skills/sanity-best-practices/SKILL.md | 74 ++ .../references/angular.md | 565 ++++++++++++++++ .../references/app-sdk.md | 462 +++++++++++++ .../sanity-best-practices/references/astro.md | 107 +++ .../references/blueprints.md | 88 +++ .../references/functions.md | 634 ++++++++++++++++++ .../references/get-started.md | 315 +++++++++ .../sanity-best-practices/references/groq.md | 350 ++++++++++ .../references/hydrogen.md | 261 +++++++ .../sanity-best-practices/references/image.md | 124 ++++ .../references/localization.md | 432 ++++++++++++ .../references/migration-html-import.md | 138 ++++ .../references/migration.md | 44 ++ .../references/nextjs.md | 544 +++++++++++++++ .../sanity-best-practices/references/nuxt.md | 84 +++ .../references/page-builder.md | 307 +++++++++ .../references/portable-text.md | 365 ++++++++++ .../references/project-structure.md | 116 ++++ .../sanity-best-practices/references/remix.md | 134 ++++ .../references/schema.md | 378 +++++++++++ .../sanity-best-practices/references/seo.md | 331 +++++++++ .../references/studio-structure.md | 136 ++++ .../references/svelte.md | 155 +++++ .../references/typegen.md | 215 ++++++ .../references/visual-editing.md | 263 ++++++++ .../skills/seo-aeo-best-practices/SKILL.md | 37 + .../references/aeo-considerations.md | 159 +++++ .../references/eeat-principles.md | 127 ++++ .../references/structured-data.md | 183 +++++ .../references/technical-seo.md | 188 ++++++ skills-lock.json | 41 ++ 53 files changed, 10190 insertions(+) create mode 100644 .agents/skills/content-experimentation-best-practices/SKILL.md create mode 100644 .agents/skills/content-experimentation-best-practices/references/cms-integration.md create mode 100644 .agents/skills/content-experimentation-best-practices/references/common-pitfalls.md create mode 100644 .agents/skills/content-experimentation-best-practices/references/experiment-design.md create mode 100644 .agents/skills/content-experimentation-best-practices/references/statistical-foundations.md create mode 100644 .agents/skills/content-modeling-best-practices/SKILL.md create mode 100644 .agents/skills/content-modeling-best-practices/references/content-reuse.md create mode 100644 .agents/skills/content-modeling-best-practices/references/reference-vs-embedding.md create mode 100644 .agents/skills/content-modeling-best-practices/references/separation-of-concerns.md create mode 100644 .agents/skills/content-modeling-best-practices/references/taxonomy-classification.md create mode 100644 .agents/skills/portable-text-conversion/SKILL.md create mode 100644 .agents/skills/portable-text-conversion/rules/html-to-pt.md create mode 100644 .agents/skills/portable-text-conversion/rules/manual-construction.md create mode 100644 .agents/skills/portable-text-conversion/rules/markdown-to-pt.md create mode 100644 .agents/skills/portable-text-serialization/SKILL.md create mode 100644 .agents/skills/portable-text-serialization/rules/astro.md create mode 100644 .agents/skills/portable-text-serialization/rules/html.md create mode 100644 .agents/skills/portable-text-serialization/rules/markdown.md create mode 100644 .agents/skills/portable-text-serialization/rules/plain-text.md create mode 100644 .agents/skills/portable-text-serialization/rules/react.md create mode 100644 .agents/skills/portable-text-serialization/rules/svelte.md create mode 100644 .agents/skills/portable-text-serialization/rules/vue.md create mode 100644 .agents/skills/sanity-best-practices/SKILL.md create mode 100644 .agents/skills/sanity-best-practices/references/angular.md create mode 100644 .agents/skills/sanity-best-practices/references/app-sdk.md create mode 100644 .agents/skills/sanity-best-practices/references/astro.md create mode 100644 .agents/skills/sanity-best-practices/references/blueprints.md create mode 100644 .agents/skills/sanity-best-practices/references/functions.md create mode 100644 .agents/skills/sanity-best-practices/references/get-started.md create mode 100644 .agents/skills/sanity-best-practices/references/groq.md create mode 100644 .agents/skills/sanity-best-practices/references/hydrogen.md create mode 100644 .agents/skills/sanity-best-practices/references/image.md create mode 100644 .agents/skills/sanity-best-practices/references/localization.md create mode 100644 .agents/skills/sanity-best-practices/references/migration-html-import.md create mode 100644 .agents/skills/sanity-best-practices/references/migration.md create mode 100644 .agents/skills/sanity-best-practices/references/nextjs.md create mode 100644 .agents/skills/sanity-best-practices/references/nuxt.md create mode 100644 .agents/skills/sanity-best-practices/references/page-builder.md create mode 100644 .agents/skills/sanity-best-practices/references/portable-text.md create mode 100644 .agents/skills/sanity-best-practices/references/project-structure.md create mode 100644 .agents/skills/sanity-best-practices/references/remix.md create mode 100644 .agents/skills/sanity-best-practices/references/schema.md create mode 100644 .agents/skills/sanity-best-practices/references/seo.md create mode 100644 .agents/skills/sanity-best-practices/references/studio-structure.md create mode 100644 .agents/skills/sanity-best-practices/references/svelte.md create mode 100644 .agents/skills/sanity-best-practices/references/typegen.md create mode 100644 .agents/skills/sanity-best-practices/references/visual-editing.md create mode 100644 .agents/skills/seo-aeo-best-practices/SKILL.md create mode 100644 .agents/skills/seo-aeo-best-practices/references/aeo-considerations.md create mode 100644 .agents/skills/seo-aeo-best-practices/references/eeat-principles.md create mode 100644 .agents/skills/seo-aeo-best-practices/references/structured-data.md create mode 100644 .agents/skills/seo-aeo-best-practices/references/technical-seo.md create mode 100644 skills-lock.json diff --git a/.agents/skills/content-experimentation-best-practices/SKILL.md b/.agents/skills/content-experimentation-best-practices/SKILL.md new file mode 100644 index 0000000..a3b3ade --- /dev/null +++ b/.agents/skills/content-experimentation-best-practices/SKILL.md @@ -0,0 +1,39 @@ +--- +name: content-experimentation-best-practices +description: Content experimentation and A/B testing guidance covering experiment design, hypotheses, metrics, sample size, statistical foundations, CMS-managed variants, and common analysis pitfalls. Use this skill when planning experiments, setting up variants, choosing success metrics, interpreting statistical results, or building experimentation workflows in a CMS or frontend stack. +--- + +# Content Experimentation Best Practices + +Principles and patterns for running effective content experiments to improve conversion rates, engagement, and user experience. + +## When to Apply + +Reference these guidelines when: +- Setting up A/B or multivariate testing infrastructure +- Designing experiments for content changes +- Analyzing and interpreting test results +- Building CMS integrations for experimentation +- Deciding what to test and how + +## Core Concepts + +### A/B Testing +Comparing two variants (A vs B) to determine which performs better. + +### Multivariate Testing +Testing multiple variables simultaneously to find optimal combinations. + +### Statistical Significance +The confidence level that results aren't due to random chance. + +### Experimentation Culture +Making decisions based on data rather than opinions (HiPPO avoidance). + +## References + +Start with the reference that matches the current problem, such as design, statistics, CMS integration, or pitfalls. See `references/` for detailed guidance: +- `references/experiment-design.md` — Hypothesis framework, metrics, sample size, and what to test +- `references/statistical-foundations.md` — p-values, confidence intervals, power analysis, Bayesian methods +- `references/cms-integration.md` — CMS-managed variants, field-level variants, external platforms +- `references/common-pitfalls.md` — 17 common mistakes across statistics, design, execution, and interpretation diff --git a/.agents/skills/content-experimentation-best-practices/references/cms-integration.md b/.agents/skills/content-experimentation-best-practices/references/cms-integration.md new file mode 100644 index 0000000..23fee0f --- /dev/null +++ b/.agents/skills/content-experimentation-best-practices/references/cms-integration.md @@ -0,0 +1,223 @@ +# CMS Integration Patterns + +Integrating experimentation with your CMS enables content teams to run tests without developer intervention. + +## Architecture Options + +### 1. CMS-Managed Variants +Store experiment variants as content in the CMS. + +**Pros:** Content team autonomy, version controlled +**Cons:** More complex queries, potential publish coordination + +```typescript +// Experiment document +defineType({ + name: 'experiment', + type: 'document', + fields: [ + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'status', type: 'string', options: { + list: ['draft', 'running', 'paused', 'concluded'] + }}), + defineField({ + name: 'variants', + type: 'array', + of: [{ + type: 'object', + fields: [ + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'weight', type: 'number' }), + defineField({ name: 'content', type: 'reference', to: [{ type: 'page' }] }), + ] + }] + }), + defineField({ name: 'startDate', type: 'datetime' }), + defineField({ name: 'endDate', type: 'datetime' }), + ] +}) +``` + +### 2. Field-Level Variants +Store variants as fields on the content document. + +**Pros:** Simpler queries, content stays together +**Cons:** Less flexible, schema complexity + +```typescript +defineType({ + name: 'landingPage', + fields: [ + defineField({ name: 'headline', type: 'string' }), + defineField({ + name: 'headlineVariantB', + type: 'string', + description: 'A/B test variant (leave empty if not testing)' + }), + defineField({ name: 'activeExperiment', type: 'string' }), + ] +}) +``` + +### 3. External Experimentation Platform +Use dedicated tools (Optimizely, LaunchDarkly, VWO) with CMS content. + +**Pros:** Robust analytics, proven platforms +**Cons:** Additional cost, integration complexity + +```typescript +// CMS stores experiment IDs, platform handles assignment +defineField({ + name: 'experimentId', + type: 'string', + description: 'Optimizely experiment ID' +}) +``` + +## Implementation Pattern (CMS-Managed) + +### 1. Experiment Schema + +```typescript +defineType({ + name: 'experiment', + type: 'document', + fields: [ + defineField({ name: 'name', type: 'string', validation: r => r.required() }), + defineField({ name: 'hypothesis', type: 'text' }), + defineField({ + name: 'status', + type: 'string', + options: { list: ['draft', 'running', 'concluded'] }, + initialValue: 'draft' + }), + defineField({ + name: 'variants', + type: 'array', + of: [{ + type: 'object', + name: 'variant', + fields: [ + defineField({ name: 'id', type: 'string' }), + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'weight', type: 'number', initialValue: 50 }), + ] + }], + validation: r => r.min(2).error('Need at least 2 variants') + }), + defineField({ name: 'targetPage', type: 'reference', to: [{ type: 'page' }] }), + defineField({ name: 'targetField', type: 'string' }), + ] +}) +``` + +### 2. Variant Content + +```typescript +// On the page being tested +defineField({ + name: 'experimentVariants', + type: 'array', + of: [{ + type: 'object', + fields: [ + defineField({ name: 'experimentId', type: 'reference', to: [{ type: 'experiment' }] }), + defineField({ name: 'variantId', type: 'string' }), + defineField({ name: 'headline', type: 'string' }), + // Other variant-specific fields + ] + }] +}) +``` + +### 3. Frontend Assignment + +```typescript +// Middleware or server-side +function assignVariant(experimentId: string, variants: Variant[]): string { + // Check for existing assignment in cookie + const cookieKey = `exp_${experimentId}` + const existing = getCookie(cookieKey) + if (existing) return existing + + // Random assignment based on weights + const rand = Math.random() * 100 + let cumulative = 0 + for (const variant of variants) { + cumulative += variant.weight + if (rand <= cumulative) { + setCookie(cookieKey, variant.id, { maxAge: 30 * 24 * 60 * 60 }) + return variant.id + } + } + return variants[0].id +} +``` + +### 4. Query with Variant + +```groq +*[_type == "page" && slug.current == $slug][0]{ + ..., + "experiment": experimentVariants[experimentId->status == "running"][0]{ + experimentId->{name, _id}, + variantId, + headline + } +} +``` + +## Analytics Integration + +### Event Tracking + +```typescript +// Track experiment exposure +function trackExposure(experimentId: string, variantId: string) { + analytics.track('Experiment Viewed', { + experimentId, + variantId, + timestamp: new Date().toISOString() + }) +} + +// Track conversion +function trackConversion(experimentId: string, variantId: string, metric: string) { + analytics.track('Experiment Conversion', { + experimentId, + variantId, + metric, + timestamp: new Date().toISOString() + }) +} +``` + +### Data Layer + +```typescript +// Push to data layer for analytics tools +window.dataLayer.push({ + event: 'experiment_assignment', + experiment_id: experimentId, + variant_id: variantId +}) +``` + +## Best Practices + +### Content Team Workflow +1. Create experiment document with hypothesis +2. Create variant content +3. Set status to "running" +4. Monitor results +5. Set status to "concluded" and record winner + +### Avoid Flicker +- Assign variants server-side when possible +- Use CSS to hide content until variant determined +- Pre-render both variants, show based on assignment + +### Clean Up +- Archive concluded experiments +- Remove losing variant content +- Implement winner as default diff --git a/.agents/skills/content-experimentation-best-practices/references/common-pitfalls.md b/.agents/skills/content-experimentation-best-practices/references/common-pitfalls.md new file mode 100644 index 0000000..cc93733 --- /dev/null +++ b/.agents/skills/content-experimentation-best-practices/references/common-pitfalls.md @@ -0,0 +1,200 @@ +# Common Experimentation Pitfalls + +Avoid these mistakes that invalidate results or lead to wrong conclusions. + +## Statistical Mistakes + +### 1. Stopping Early (Peeking) + +**The problem:** Checking results daily and stopping when you see significance. + +**Why it's wrong:** Statistical significance fluctuates. At any point during a test, you might see "significance" that disappears with more data. This is called the "peeking problem" or "repeated significance testing." + +**The fix:** +- Pre-calculate required sample size +- Commit to running until you reach it +- If you must peek, use sequential testing methods that account for multiple looks + +### 2. Underpowered Tests + +**The problem:** Running tests without enough traffic to detect realistic effect sizes. + +**Why it's wrong:** You'll conclude "no difference" when there actually is one—you just couldn't detect it. + +**The fix:** +- Calculate required sample size before starting +- Be realistic about minimum detectable effect (can you act on a 0.5% improvement?) +- If traffic is low, test bigger changes + +### 3. Multiple Comparisons + +**The problem:** Testing many variants or metrics and celebrating any that reach significance. + +**Why it's wrong:** With 20 metrics, you expect 1 false positive at 95% confidence—by chance alone. + +**The fix:** +- Define ONE primary metric before starting +- Use Bonferroni correction or similar for multiple comparisons +- Treat secondary metrics as directional, not conclusive + +### 4. Ignoring Segments + +**The problem:** Only looking at aggregate results. + +**Why it's wrong:** Simpson's Paradox—overall winner might be loser for your key segments. + +**The fix:** +- Always segment by device, traffic source, user type +- Check if results are consistent across segments +- If segments differ dramatically, investigate why + +## Design Mistakes + +### 5. Testing Too Many Things + +**The problem:** Changing headline, image, CTA, and layout simultaneously. + +**Why it's wrong:** You won't know which change caused the result. And each variable multiplies required sample size. + +**The fix:** +- Test one variable at a time (A/B testing) +- If testing multiple, use proper multivariate testing with adequate sample size +- Prioritize highest-impact changes first + +### 6. Vague Hypothesis + +**The problem:** "Let's see if this new design is better." + +**Why it's wrong:** Without a hypothesis, you can't learn WHY something worked (or didn't). + +**The fix:** +- State: "We believe [change] will [impact metric] because [reasoning]" +- Even if you're wrong, you learn something + +### 7. No Control + +**The problem:** Changing the control during the test, or not having one. + +**Why it's wrong:** You need a stable baseline to compare against. + +**The fix:** +- Never modify the control mid-test +- If you must change it, start a new test +- Document exactly what the control is + +## Execution Mistakes + +### 8. External Contamination + +**The problem:** Running a test during a sale, holiday, or major event. + +**Why it's wrong:** External factors affect both variants differently, contaminating results. + +**The fix:** +- Avoid tests during unusual periods +- If unavoidable, note it and extend the test past the event +- Compare to the same period historically + +### 9. Selection Bias + +**The problem:** Testing on a non-representative sample (e.g., only logged-in users). + +**Why it's wrong:** Results won't generalize to your full audience. + +**The fix:** +- Test on representative traffic +- Be explicit about who's included/excluded +- Note limitations when reporting results + +### 10. Implementation Bugs + +**The problem:** Variants don't render correctly, tracking fires incorrectly, assignment is biased. + +**Why it's wrong:** You're not testing what you think you're testing. + +**The fix:** +- QA both variants thoroughly before launch +- Verify tracking events fire correctly +- Check assignment distribution matches weights + +## Interpretation Mistakes + +### 11. Celebrating Trivial Wins + +**The problem:** Implementing a change because it was "statistically significant" even though the effect was tiny. + +**Why it's wrong:** Statistical significance ≠ practical significance. A 0.01% improvement isn't worth the complexity. + +**The fix:** +- Define minimum meaningful effect before starting +- Consider implementation cost vs. benefit +- Don't over-optimize + +### 12. Ignoring Confidence Intervals + +**The problem:** Only reporting point estimates ("5% improvement!"). + +**Why it's wrong:** The true effect could be anywhere in the confidence interval. + +**The fix:** +- Report confidence intervals: "5% improvement (95% CI: 2%-8%)" +- Base decisions on the lower bound for conservative estimates +- Wider intervals = more uncertainty + +### 13. Not Documenting Learnings + +**The problem:** Running tests but not recording what you learned. + +**Why it's wrong:** You'll repeat mistakes, forget context, lose institutional knowledge. + +**The fix:** +- Document every test: hypothesis, results, learnings +- Include what surprised you +- Build a searchable knowledge base + +## Organizational Mistakes + +### 14. HiPPO (Highest Paid Person's Opinion) + +**The problem:** Running experiments but ignoring results when leadership disagrees. + +**Why it's wrong:** Defeats the purpose of data-driven decision making. + +**The fix:** +- Get buy-in before testing that results will be honored +- Present data clearly to stakeholders +- Frame as "learning" not "winning/losing" + +### 15. Testing Everything + +**The problem:** Running experiments on trivial changes that don't matter. + +**Why it's wrong:** Wastes resources, creates testing fatigue, delays important experiments. + +**The fix:** +- Prioritize tests by potential impact +- Not everything needs a test—use judgment for low-risk changes +- Focus experimentation resources on high-value decisions + +### 16. Sample Ratio Mismatch (SRM) + +**The problem:** The actual traffic split doesn't match the intended split (e.g., you expect 50/50 but observe 52/48). + +**Why it's wrong:** SRM is a strong signal of an implementation bug — broken randomization, bot contamination, or redirect issues. Results from experiments with SRM cannot be trusted. + +**The fix:** +- Check the actual split ratio against expected before analyzing results +- Use a chi-squared test to detect statistically significant mismatches +- If SRM is detected, investigate the root cause before drawing any conclusions +- Common causes: bot traffic, browser redirects dropping users, bucketing bugs + +### 17. Novelty and Primacy Effects + +**The problem:** Users react differently to new designs initially, and the effect fades over time. + +**Why it's wrong:** Short experiments may show inflated effects that don't persist. Returning users may click more simply because something looks new. + +**The fix:** +- Run experiments for at least 2 full business cycles +- Segment results by new vs. returning users +- If possible, check whether the effect holds in the second week vs. the first diff --git a/.agents/skills/content-experimentation-best-practices/references/experiment-design.md b/.agents/skills/content-experimentation-best-practices/references/experiment-design.md new file mode 100644 index 0000000..87f9697 --- /dev/null +++ b/.agents/skills/content-experimentation-best-practices/references/experiment-design.md @@ -0,0 +1,109 @@ +# Experiment Design Principles + +Well-designed experiments produce actionable insights. Poorly designed ones waste time and can mislead. + +## The Experiment Framework + +### 1. Hypothesis +State what you believe and why. + +**Bad:** "Let's test a new headline" +**Good:** "We believe a benefit-focused headline will increase signup rate by 10% because users are currently confused about our value proposition" + +Structure: "We believe [change] will [impact metric] because [reasoning]" + +### 2. Success Metric +Define primary and guardrail metrics. + +**Primary metric:** The main thing you're trying to improve (conversion rate, engagement time) +**Guardrail metrics:** Things that shouldn't get worse (bounce rate, page load time) + +### 3. Sample Size +Calculate required sample size before starting. + +Factors: +- Baseline conversion rate +- Minimum detectable effect (MDE) +- Statistical significance level (usually 95%) +- Statistical power (usually 80%) + +Use calculators like [Evan Miller's](https://www.evanmiller.org/ab-testing/sample-size.html). + +### 4. Duration +Run tests for full business cycles. + +- Minimum: 1-2 weeks (capture weekly patterns) +- Include weekends +- Avoid holidays and major events +- Don't stop early when you see "winning" results + +## What to Test + +### High-Impact Areas +- Headlines and value propositions +- Call-to-action text and placement +- Form length and fields +- Pricing presentation +- Social proof placement + +### Lower-Impact (Usually) +- Button colors +- Minor copy tweaks +- Image variations (unless hero) +- Footer changes + +### Test Priority Matrix + +| Impact | Effort | Priority | +|--------|--------|----------| +| High | Low | Do first | +| High | High | Plan carefully | +| Low | Low | Quick wins | +| Low | High | Avoid | + +## Sanity Integration Pattern + +```typescript +// Experiment variant schema +defineType({ + name: 'experimentVariant', + type: 'object', + fields: [ + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'weight', type: 'number', description: 'Traffic allocation (0-100)' }), + defineField({ name: 'content', type: 'reference', to: [{ type: 'page' }] }), + ] +}) + +// Experiment document +defineType({ + name: 'experiment', + type: 'document', + fields: [ + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'hypothesis', type: 'text' }), + defineField({ name: 'status', type: 'string', options: { + list: ['draft', 'running', 'concluded'] + }}), + defineField({ name: 'variants', type: 'array', of: [{ type: 'experimentVariant' }] }), + defineField({ name: 'startDate', type: 'datetime' }), + defineField({ name: 'endDate', type: 'datetime' }), + defineField({ name: 'winner', type: 'string' }), + defineField({ name: 'learnings', type: 'text' }), + ] +}) +``` + +## Avoiding Common Mistakes + +### Don't peek and stop early +Statistical significance can fluctuate. Commit to your sample size. + +### Don't test too many things at once +Each variable multiplies required sample size. + +### Don't ignore segmentation +Winners may differ by device, traffic source, or user type. + +### Document everything +Future you (and your team) will thank you. diff --git a/.agents/skills/content-experimentation-best-practices/references/statistical-foundations.md b/.agents/skills/content-experimentation-best-practices/references/statistical-foundations.md new file mode 100644 index 0000000..415f3c6 --- /dev/null +++ b/.agents/skills/content-experimentation-best-practices/references/statistical-foundations.md @@ -0,0 +1,150 @@ +# Statistical Foundations + +Understanding basic statistics prevents misinterpreting experiment results. + +## Table of Contents + +- Key concepts +- Sample size calculation +- Common statistical mistakes +- Interpreting results +- Alternative approaches +- When to trust results + +## Key Concepts + +### Statistical Significance + +A measure of whether observed differences are likely real or due to chance. + +- **p-value < 0.05:** "Statistically significant" at 95% confidence +- Means: If there were no real difference, there's less than a 5% chance of seeing results this extreme +- Does NOT mean: The change is important or meaningful +- **Common misconception:** The p-value is NOT "the probability the result is due to chance." It's the probability of observing data this extreme *assuming* the null hypothesis is true. + +### Confidence Interval + +A range of plausible values for the true effect. + +Example: "Conversion rate increased by 5% (95% CI: 2% to 8%)" +- Best estimate: 5% improvement +- Could be as low as 2% or as high as 8% +- Narrower intervals = more certainty + +### Statistical Power + +The ability to detect a real effect when it exists. + +- Standard: 80% power +- Higher power = larger sample size needed +- Low power = might miss real improvements + +### Minimum Detectable Effect (MDE) + +The smallest improvement worth detecting. + +- Smaller MDE = larger sample size needed +- Be realistic: Can you act on a 0.5% improvement? + +## Sample Size Calculation + +Before running a test, calculate required sample size: + +``` +Required per variant = 16 × σ² / MDE² + +Where: +- σ² = variance (for conversion rate: p × (1-p)) +- MDE = minimum detectable effect (absolute) +``` + +For a 5% baseline conversion rate, detecting a 1% absolute lift (5% → 6%): +- σ² = 0.05 × 0.95 = 0.0475 +- MDE² = 0.01² = 0.0001 +- n = 16 × 0.0475 / 0.0001 = **7,600 per variant** +- Total: ~15,200 visitors minimum + +## Common Statistical Mistakes + +### Multiple Comparisons Problem + +Testing 10 variants increases false positive rate. + +**Solution:** Adjust significance threshold (Bonferroni correction) or use sequential testing methods. + +### Peeking Problem + +Checking results daily and stopping when significant. + +**Why it's wrong:** Significance fluctuates. Early "winners" often regress. + +**Solution:** Pre-commit to sample size and duration. Use sequential testing if you must peek. + +### Simpson's Paradox + +Overall results hide segmented truths. + +Example: +- Overall: Variant B wins +- Mobile users: Variant A wins +- Desktop users: Variant A wins +- How? Different traffic mix per variant + +**Solution:** Always segment by major factors (device, traffic source). + +### Survivorship Bias + +Only analyzing users who completed the funnel. + +**Solution:** Include all visitors, not just converters. + +## Interpreting Results + +### Significant + Meaningful +Clear win. Implement the change. + +### Significant + Trivial +Statistically different but tiny effect. Consider if worth the complexity. + +### Not Significant + Large Effect +Might be real but underpowered. Extend the test or accept uncertainty. + +### Not Significant + Small Effect +No detectable difference. Either no real effect or test was underpowered. + +## Alternative Approaches + +### Bayesian A/B Testing + +An alternative to traditional (frequentist) hypothesis testing. Bayesian methods provide: +- **Direct probability statements:** "There's a 95% probability Variant B is better" (more intuitive than p-values) +- **No peeking problem:** Continuous monitoring is built in — you can check results at any time +- **Credible intervals:** Directly interpretable as "the true value falls in this range with X% probability" + +Bayesian methods are offered by platforms like VWO and are useful when you need to make decisions with limited traffic or want more intuitive reporting for stakeholders. + +### Multi-Armed Bandits + +Dynamically allocate more traffic to winning variants while still learning: +- **Thompson Sampling:** Balances exploration (learning) with exploitation (serving the best variant) +- **Best for:** Ongoing optimization where you want to minimize regret during the test +- **Trade-off:** Faster convergence to the winner, but less statistical rigor than fixed-allocation A/B tests + +Consider bandits for content recommendations, personalization, or situations where the cost of showing a losing variant is high. + +### Sequential Testing + +For teams that need to monitor experiments continuously: +- **Group sequential designs** (O'Brien-Fleming, Lan-DeMets) allow pre-planned interim analyses +- **Always-valid p-values** let you check results at any time without inflating false positive rates +- Use when you must balance the peeking problem with business pressure to act on results quickly + +## When to Trust Results + +Checklist before declaring a winner: +- [ ] Reached pre-calculated sample size +- [ ] Ran for full business cycle (1-2 weeks minimum) +- [ ] p-value < 0.05 (or your chosen threshold) +- [ ] Effect size is meaningful for business +- [ ] Results consistent across major segments +- [ ] No external factors contaminated results diff --git a/.agents/skills/content-modeling-best-practices/SKILL.md b/.agents/skills/content-modeling-best-practices/SKILL.md new file mode 100644 index 0000000..98d5a7c --- /dev/null +++ b/.agents/skills/content-modeling-best-practices/SKILL.md @@ -0,0 +1,32 @@ +--- +name: content-modeling-best-practices +description: Structured content modeling guidance for schema design, content architecture, content reuse, references versus embedded objects, separation of concerns, and taxonomies across Sanity and other headless CMSes. Use this skill when designing or refactoring content types, deciding field shapes, debating reusable versus nested content, planning omnichannel content models, or reviewing whether a schema is too page-shaped or presentation-driven. +--- + +# Content Modeling Best Practices + +Principles for designing structured content that's flexible, reusable, and maintainable. These concepts apply to any headless CMS but include Sanity-specific implementation notes. + +## When to Apply + +Reference these guidelines when: +- Starting a new project and designing the content model +- Evaluating whether content should be structured or free-form +- Deciding between references and embedded content +- Planning for multi-channel content delivery +- Refactoring existing content structures + +## Core Principles + +1. **Content is data, not pages** — Structure content for meaning, not presentation +2. **Single source of truth** — Avoid content duplication +3. **Future-proof** — Design for channels that don't exist yet +4. **Editor-centric** — Optimize for the people creating content + +## References + +Start with the reference that matches the modeling decision in front of you, instead of loading every topic at once. See `references/` for detailed guidance on specific topics: +- `references/separation-of-concerns.md` — Separating content from presentation +- `references/reference-vs-embedding.md` — When to use references vs embedded objects +- `references/content-reuse.md` — Content reuse patterns and the reuse spectrum +- `references/taxonomy-classification.md` — Flat, hierarchical, and faceted classification diff --git a/.agents/skills/content-modeling-best-practices/references/content-reuse.md b/.agents/skills/content-modeling-best-practices/references/content-reuse.md new file mode 100644 index 0000000..2f31e0a --- /dev/null +++ b/.agents/skills/content-modeling-best-practices/references/content-reuse.md @@ -0,0 +1,134 @@ +# Content Reuse Patterns + +Effective content models maximize reuse while minimizing duplication. Here are patterns for achieving both. + +## The Content Reuse Spectrum + +``` +Full Duplication ←————————————————→ Full Reference +(Copy everything) (Link to one source) +``` + +Most real-world content sits somewhere in between. + +## Pattern 1: Shared Components + +Create reusable content blocks that can be embedded anywhere. + +**Use case:** Testimonials, FAQs, CTAs that appear on multiple pages. + +```typescript +// Standalone testimonial documents +defineType({ + name: 'testimonial', + type: 'document', + fields: [ + defineField({ name: 'quote', type: 'text' }), + defineField({ name: 'author', type: 'string' }), + defineField({ name: 'company', type: 'string' }), + ] +}) + +// Reference in page builders +defineField({ + name: 'pageBuilder', + type: 'array', + of: [ + { type: 'reference', to: [{ type: 'testimonial' }] } + ] +}) +``` + +## Pattern 2: Shared Field Sets + +Extract common fields into reusable definitions. + +**Use case:** SEO fields, social metadata, common dates. + +```typescript +// Shared field definition +export const seoFields = [ + defineField({ name: 'seoTitle', type: 'string' }), + defineField({ name: 'seoDescription', type: 'text' }), + defineField({ name: 'ogImage', type: 'image' }), +] + +// Spread into multiple types +defineType({ + name: 'page', + fields: [ + defineField({ name: 'title', type: 'string' }), + ...seoFields + ] +}) + +defineType({ + name: 'post', + fields: [ + defineField({ name: 'title', type: 'string' }), + ...seoFields + ] +}) +``` + +## Pattern 3: Taxonomy References + +Centralize classification for consistent tagging. + +**Use case:** Categories, tags, topics that span content types. + +```typescript +// Central taxonomy +defineType({ + name: 'category', + type: 'document', + fields: [ + defineField({ name: 'title', type: 'string' }), + defineField({ name: 'slug', type: 'slug' }), + ] +}) + +// Used across content types +defineField({ + name: 'categories', + type: 'array', + of: [{ type: 'reference', to: [{ type: 'category' }] }] +}) +``` + +## Pattern 4: Content Fragments + +Small, reusable pieces that combine into larger content. + +**Use case:** Bios, addresses, contact info. + +```typescript +// Fragment type +defineType({ + name: 'contactInfo', + type: 'object', + fields: [ + defineField({ name: 'email', type: 'email' }), + defineField({ name: 'phone', type: 'string' }), + defineField({ name: 'address', type: 'text' }), + ] +}) + +// Reused across types +defineType({ + name: 'office', + fields: [ + defineField({ name: 'name', type: 'string' }), + defineField({ name: 'contact', type: 'contactInfo' }), + ] +}) +``` + +## Anti-Pattern: Over-Abstraction + +Not everything needs to be reusable. If content is only used in one place, embedding is simpler. + +**Signs of over-abstraction:** +- References that are only used once +- Editors navigating multiple documents for one page +- Complex queries joining rarely-shared content diff --git a/.agents/skills/content-modeling-best-practices/references/reference-vs-embedding.md b/.agents/skills/content-modeling-best-practices/references/reference-vs-embedding.md new file mode 100644 index 0000000..52098a5 --- /dev/null +++ b/.agents/skills/content-modeling-best-practices/references/reference-vs-embedding.md @@ -0,0 +1,89 @@ +# Reference vs Embedding Content + +When should content be linked (referenced) vs copied (embedded)? This decision affects reusability, query complexity, and editing workflows. + +## The Trade-offs + +| Aspect | Reference | Embedded Object | +|--------|-----------|-----------------| +| Reusability | ✅ Shared across documents | ❌ Copied per document | +| Single source | ✅ Update once, reflects everywhere | ❌ Must update each copy | +| Query complexity | Requires joins/expansion | Inline, simpler queries | +| Editing UX | Separate editing interface | All fields in one place | +| Independence | Can exist on its own | Only exists within parent | + +## When to Reference + +Use references when content: +- **Is reusable** — Same author across many articles +- **Needs central management** — Update product info once +- **Has its own lifecycle** — Published/draft independent of parent +- **Should stay in sync** — Price changes reflect everywhere + +**Examples:** +- Author profiles +- Product catalog items +- Shared testimonials +- Category taxonomy +- Reusable CTAs + +## When to Embed + +Use embedded objects when content: +- **Is unique to this document** — Page-specific hero +- **Doesn't make sense alone** — SEO metadata +- **Should be copied, not linked** — Historical snapshot +- **Simplifies editing** — All fields in one form + +**Examples:** +- SEO metadata +- Page-specific sections +- Address information +- Social links +- Configuration options + +## Sanity Implementation + +```typescript +// Reference: Author is reusable +defineField({ + name: 'author', + type: 'reference', + to: [{ type: 'author' }] +}) + +// Embedded: SEO is page-specific +defineField({ + name: 'seo', + type: 'object', + fields: [ + defineField({ name: 'title', type: 'string' }), + defineField({ name: 'description', type: 'text' }) + ] +}) +``` + +## The Hybrid Approach + +Sometimes you want both: a reference for the canonical data, plus embedded overrides. + +```typescript +defineField({ + name: 'featuredProduct', + type: 'object', + fields: [ + defineField({ + name: 'product', + type: 'reference', + to: [{ type: 'product' }] + }), + defineField({ + name: 'overrideTitle', + type: 'string', + description: 'Optional: Override the product title for this context' + }), + ] +}) +``` + +Query uses `coalesce(overrideTitle, product->title)`. diff --git a/.agents/skills/content-modeling-best-practices/references/separation-of-concerns.md b/.agents/skills/content-modeling-best-practices/references/separation-of-concerns.md new file mode 100644 index 0000000..5ce2085 --- /dev/null +++ b/.agents/skills/content-modeling-best-practices/references/separation-of-concerns.md @@ -0,0 +1,60 @@ +# Separation of Content and Presentation + +The most important principle in structured content: **separate what content IS from how it LOOKS**. + +## The Problem + +When content is tied to presentation: +- Redesigns require content migration +- Content can't be reused across channels (web, mobile, voice) +- Editors make design decisions instead of content decisions +- A/B testing requires duplicate content + +## The Principle + +Model content based on **meaning and purpose**, not visual appearance. + +### Bad: Presentation-Focused + +``` +BigHeroText → What if we want small heroes? +RedButton → What if brand colors change? +ThreeColumnLayout → What if mobile needs one column? +LeftSidebar → Position is a frontend concern +MobileImage → Device-specific content is fragile +``` + +### Good: Meaning-Focused + +``` +Headline → The main message (render however) +CallToAction → An action we want users to take +Features → A list of things (columns decided by frontend) +RelatedContent → Content relationships (position by context) +Image → One image with responsive crops +``` + +## Testing Your Model + +Ask: "If we completely redesigned the site, would these field names still make sense?" + +- `threeColumnFeatures` → ❌ Fails (what if 2 columns?) +- `features` → ✅ Works (describes the content's purpose: a list of product features) +- `blueHighlightBox` → ❌ Fails (what if we go purple?) +- `callout` → ✅ Works (describes the content's role: an attention-grabbing aside) + +## Sanity Implementation + +```typescript +// ❌ Avoid presentation-focused names +defineField({ name: 'bigHeroText', type: 'string' }) +defineField({ name: 'fontSize', type: 'number' }) +defineField({ name: 'backgroundColor', type: 'color' }) + +// ✅ Use meaning-focused names +defineField({ name: 'headline', type: 'string' }) +defineField({ name: 'emphasis', type: 'string', options: { list: ['standard', 'prominent'] } }) +defineField({ name: 'tone', type: 'string', options: { list: ['neutral', 'warning', 'success'] } }) +``` + +The frontend translates `tone: 'warning'` to visual styles. Content stays semantic. diff --git a/.agents/skills/content-modeling-best-practices/references/taxonomy-classification.md b/.agents/skills/content-modeling-best-practices/references/taxonomy-classification.md new file mode 100644 index 0000000..d8c077d --- /dev/null +++ b/.agents/skills/content-modeling-best-practices/references/taxonomy-classification.md @@ -0,0 +1,136 @@ +# Taxonomy and Classification + +Organizing content with taxonomies enables filtering, navigation, and content relationships. Well-designed taxonomies scale; poorly designed ones become maintenance nightmares. + +## Types of Classification + +### Flat Taxonomy +Simple list of terms with no hierarchy. + +**Use for:** Tags, simple categories +**Example:** Blog tags: "javascript", "react", "tutorial" + +```typescript +defineType({ + name: 'tag', + type: 'document', + fields: [ + defineField({ name: 'title', type: 'string' }), + defineField({ name: 'slug', type: 'slug' }), + ] +}) +``` + +### Hierarchical Taxonomy +Terms with parent-child relationships. + +**Use for:** Product categories, content sections +**Example:** Electronics > Phones > Smartphones + +```typescript +defineType({ + name: 'category', + type: 'document', + fields: [ + defineField({ name: 'title', type: 'string' }), + defineField({ name: 'slug', type: 'slug' }), + defineField({ + name: 'parent', + type: 'reference', + to: [{ type: 'category' }], + description: 'Parent category (leave empty for top-level)' + }), + ] +}) +``` + +### Faceted Classification +Multiple independent dimensions. + +**Use for:** Complex filtering (e-commerce) +**Example:** Filter by color AND size AND price range + +```typescript +// Multiple taxonomy types +defineField({ name: 'color', type: 'reference', to: [{ type: 'color' }] }) +defineField({ name: 'size', type: 'reference', to: [{ type: 'size' }] }) +defineField({ name: 'material', type: 'reference', to: [{ type: 'material' }] }) +``` + +## Design Principles + +### 1. Mutual Exclusivity (When Appropriate) +Categories should be distinct. If items frequently belong to multiple categories, consider tags instead. + +**Categories:** One primary classification +**Tags:** Many optional classifications + +### 2. User-Centric Naming +Use terms your audience uses, not internal jargon. + +**Bad:** "Content Assets" (internal term) +**Good:** "Resources" or "Downloads" (user term) + +### 3. Balanced Depth +Too shallow: Everything lumped together +Too deep: Users can't find anything + +**Rule of thumb:** 3-4 levels max for hierarchies + +### 4. Scalable Structure +Design for 10x growth. Will your structure work with 10,000 items? + +## Querying Taxonomies + +### Get all items in a category + +```groq +*[_type == "product" && category._ref == $categoryId] +``` + +### Get items in category OR children + +```groq +// First get all descendant category IDs +*[_type == "product" && category._ref in + *[_type == "category" && ( + _id == $categoryId || + parent._ref == $categoryId || + parent->parent._ref == $categoryId + )]._id +] +``` + +### Get category tree + +```groq +*[_type == "category" && !defined(parent)]{ + title, + slug, + "children": *[_type == "category" && parent._ref == ^._id]{ + title, + slug, + "children": *[_type == "category" && parent._ref == ^._id]{ + title, + slug + } + } +} +``` + +## Common Mistakes + +### Over-categorization +Creating a category for everything results in mostly-empty categories. + +**Fix:** Start minimal, add categories as content grows. + +### Inconsistent Granularity +Some categories broad ("Technology"), others narrow ("React 18 Server Components"). + +**Fix:** Define clear criteria for category creation. + +### No Governance +Anyone can create taxonomy terms, leading to duplicates and inconsistency. + +**Fix:** Limit who can create/edit taxonomy documents. Use validation. diff --git a/.agents/skills/portable-text-conversion/SKILL.md b/.agents/skills/portable-text-conversion/SKILL.md new file mode 100644 index 0000000..9965873 --- /dev/null +++ b/.agents/skills/portable-text-conversion/SKILL.md @@ -0,0 +1,65 @@ +--- +name: portable-text-conversion +description: Convert HTML and Markdown content into Portable Text blocks for Sanity. Use when migrating content from legacy CMSs, importing HTML or Markdown into Sanity, building content pipelines that ingest external content, converting rich text between formats, or programmatically creating Portable Text documents. Covers @portabletext/markdown (markdownToPortableText), @portabletext/block-tools (htmlToBlocks), custom deserializers, and the Portable Text specification for manual block construction. +license: MIT +metadata: + author: sanity + version: "1.0.0" +--- + +# Portable Text Conversion + +Convert external content (HTML, Markdown) into Portable Text for Sanity. Three main approaches: + +1. **`markdownToPortableText`** — Convert Markdown directly using `@portabletext/markdown` (recommended for Markdown) +2. **`htmlToBlocks`** — Parse HTML into PT blocks using `@portabletext/block-tools` (for HTML migration) +3. **Manual construction** — Build PT blocks directly from any source (APIs, databases, etc.) + +## Portable Text Specification + +Understand the target format before converting. PT is an array of blocks: + +```json +[ + { + "_type": "block", + "_key": "abc123", + "style": "normal", + "children": [ + {"_type": "span", "_key": "def456", "text": "Hello ", "marks": []}, + {"_type": "span", "_key": "ghi789", "text": "world", "marks": ["strong"]} + ], + "markDefs": [] + }, + { + "_type": "block", + "_key": "jkl012", + "style": "h2", + "children": [ + {"_type": "span", "_key": "mno345", "text": "A heading", "marks": []} + ], + "markDefs": [] + }, + { + "_type": "image", + "_key": "pqr678", + "asset": {"_type": "reference", "_ref": "image-abc-200x200-png"} + } +] +``` + +**Key rules:** +- Every block and span needs `_key` (unique within the array) +- `_type: "block"` is for text blocks; custom types use their own `_type` +- `markDefs` holds annotation data; `marks` on spans reference `markDefs[*]._key` or are decorator strings +- Lists use `listItem` ("bullet" | "number") and `level` (1, 2, 3...) on regular blocks + +## Conversion Rules + +Read the rule file matching your source format: + +- **Markdown → Portable Text**: `rules/markdown-to-pt.md` — `@portabletext/markdown` with `markdownToPortableText` (recommended) +- **HTML → Portable Text**: `rules/html-to-pt.md` — `@portabletext/block-tools` with `htmlToBlocks` +- **Manual PT Construction**: `rules/manual-construction.md` — build blocks programmatically from any source + +> **Note:** `@sanity/block-tools` is the legacy package name. Always use `@portabletext/block-tools` for new projects. The API is the same. diff --git a/.agents/skills/portable-text-conversion/rules/html-to-pt.md b/.agents/skills/portable-text-conversion/rules/html-to-pt.md new file mode 100644 index 0000000..153e88b --- /dev/null +++ b/.agents/skills/portable-text-conversion/rules/html-to-pt.md @@ -0,0 +1,242 @@ +--- +title: Convert HTML to Portable Text +description: Use @portabletext/block-tools with htmlToBlocks to convert HTML content into Portable Text blocks +tags: [portable-text, html, conversion, migration, import] +--- + +# Convert HTML to Portable Text + +Use `@portabletext/block-tools` to parse HTML into Portable Text blocks. This is the primary tool for migrating HTML content from legacy CMSs. It has built-in support for content from Google Docs, Microsoft Word, and Notion. + +> **Note:** For Markdown sources, use `@portabletext/markdown` instead — it's simpler and more direct. See `rules/markdown-to-pt.md`. + +> **Note:** `@sanity/block-tools` is the legacy package name. Use `@portabletext/block-tools` for new projects. The API is identical. + +## Setup + +```bash +npm install @portabletext/block-tools jsdom @sanity/schema +``` + +In Node.js, you must provide a `parseHtml` function that returns a DOM `Document`. Use JSDOM for this: + +```ts +import {htmlToBlocks} from '@portabletext/block-tools' +import {JSDOM} from 'jsdom' +import Schema from '@sanity/schema' + +// JSDOM is passed to htmlToBlocks via the parseHtml option: +// htmlToBlocks(html, blockContentType, { +// parseHtml: (html) => new JSDOM(html).window.document, +// }) +``` + +## Define Your Schema + +`htmlToBlocks` needs a compiled Sanity block content type to know which marks, styles, and custom types are valid. Use `@sanity/schema` to compile it: + +```ts +const defaultSchema = Schema.compile({ + name: 'mySchema', + types: [ + { + name: 'post', + type: 'document', + fields: [ + { + name: 'body', + type: 'array', + of: [ + { + type: 'block', + marks: { + decorators: [ + {title: 'Strong', value: 'strong'}, + {title: 'Emphasis', value: 'em'}, + {title: 'Code', value: 'code'}, + ], + annotations: [ + { + name: 'link', + type: 'object', + fields: [{name: 'href', type: 'url'}], + }, + ], + }, + styles: [ + {title: 'Normal', value: 'normal'}, + {title: 'H2', value: 'h2'}, + {title: 'H3', value: 'h3'}, + {title: 'Quote', value: 'blockquote'}, + ], + lists: [ + {title: 'Bullet', value: 'bullet'}, + {title: 'Number', value: 'number'}, + ], + }, + { + name: 'image', + type: 'image', + fields: [{name: 'alt', type: 'string'}], + }, + ], + }, + ], + }, + ], +}) + +const blockContentType = defaultSchema + .get('post') + .fields.find((f) => f.name === 'body').type +``` + +## Basic Conversion + +```ts +const html = '

Hello world

Heading

' + +const blocks = htmlToBlocks(html, blockContentType, { + parseHtml: (html) => new JSDOM(html).window.document, +}) +``` + +## Custom Deserializers + +Handle HTML elements that don't map directly to standard PT: + +```ts +const blocks = htmlToBlocks(html, blockContentType, { + parseHtml: (html) => new JSDOM(html).window.document, + rules: [ + // Convert to image blocks + { + deserialize(el, next, block) { + if (el.tagName?.toLowerCase() !== 'img') return undefined + + return block({ + _type: 'image', + asset: { + _type: 'reference', + _ref: '', // Upload image separately, set ref after + }, + alt: el.getAttribute('alt') || '', + _sanityAsset: `image@${el.getAttribute('src')}`, // for migration tooling + }) + }, + }, + // Convert with custom attributes + { + deserialize(el, next, block) { + if (el.tagName?.toLowerCase() !== 'a') return undefined + + const href = el.getAttribute('href') || '' + const target = el.getAttribute('target') || '' + + return { + _type: '__annotation', + markDef: { + _type: 'link', + href, + ...(target ? {target} : {}), + }, + children: next(el.childNodes), + } + }, + }, + // Convert