Skip to content

feat: campaign funnel map — JSON graph + validation (#10)#11

Closed
brettstirlingbond wants to merge 7 commits into
mainfrom
claude/distracted-ardinghelli
Closed

feat: campaign funnel map — JSON graph + validation (#10)#11
brettstirlingbond wants to merge 7 commits into
mainfrom
claude/distracted-ardinghelli

Conversation

@brettstirlingbond
Copy link
Copy Markdown
Contributor

@brettstirlingbond brettstirlingbond commented Apr 15, 2026

Summary

Adds build-time funnel graph serialization and validation for campaign pages. During npm run build, the tool now:

  • Collects routing data from frontmatter fields (next_success_url, next_upsell_accept, next_upsell_decline)
  • Builds a directed graph (nodes + edges) per campaign
  • Validates the graph against 6 rules: broken links, orphan pages, missing terminal, asymmetric upsell, missing entry point, duplicate page ID
  • Writes funnel.json to .cpk/{slug}/ (developer-only, not deployed)
  • Exits non-zero on validation errors (or --lenient to downgrade to warnings)

Consumers can build their own visualization from the stable JSON schema (D3, Cytoscape, Mermaid, etc.).

Closes #10

Changes

  • lib/engine/funnel.js (new) — graph generation, 6 validation rules (BFS + reverse BFS), JSON output
  • lib/engine/build.js — funnel pipeline integration, .cpk/ output, partial rebuild safety, lenient mode
  • lib/config.jsgetCpkPath(), shared parseFlag() and formatBuildSummary() helpers
  • lib/actions/build.js--lenient flag, funnelErrors in exit code, uses shared helpers
  • lib/actions/dev.js — lenient by default, --strict override, funnelErrors surfaced, uses shared helpers
  • index.js — exports generateFunnelMap and validateFunnel for programmatic use
  • .gitignore.cpk/ excluded from version control

Validation rules (6)

  1. Duplicate page — two pages resolve to same node ID
  2. Broken link — edge target not in campaign nodes
  3. Orphan page — unreachable from entry point (BFS)
  4. Missing terminal — no path to receipt page (reverse BFS, catches cycles)
  5. Asymmetric upsell — accept without decline or vice versa
  6. Missing entry point — no index page in campaign

Security

  • urlToNodeId guards against external URLs, protocol-relative, mailto:, tel:, javascript:, and other URI schemes
  • Query strings and fragments stripped before node ID resolution

Documentation

  • docs/funnel-map.md — comprehensive guide covering the JSON schema (with field tables), all 6 validation rules with example errors and fixes, CLI flag behavior (--lenient / --strict), a complete 4-page demo campaign, and the programmatic API
  • docs/examples/demo-funnel/ — copy-pasteable 4-page campaign (product → checkout → upsell → receipt) with minimal layout; users can drop it into their own src/ to see the full flow
  • README.md — new "Funnel Map" section with quickstart and link to the full guide; Commands table documents --lenient and --strict flags

Test plan

  • 137 tests pass, 0 failures
  • 99.3% statement coverage
  • Valid 4-page funnel builds with funnel.json in .cpk/
  • Broken funnel (all 6 rules) produces correct errors
  • --lenient downgrades errors to warnings, exits zero
  • External/protocol-relative/URI-scheme URLs produce 0 edges
  • Query strings and fragments stripped from URLs
  • Custom permalinks resolve correctly in graph
  • Partial rebuild skips funnel regeneration (preserves existing artifacts)
  • writeFunnelJson failure is warning-only (never blocks build)
  • Duplicate node IDs detected and reported
  • Programmatic API: require('next-campaign-page-kit').generateFunnelMap
  • funnel.html is NOT generated (JSON-only output enforced by test)

Scope note

An HTML/SVG visualization was previously included in this PR but was removed per Product Owner direction — the JSON schema is the stable contract, and consumers are free to build whatever visualization fits their stack.

🤖 Generated with Claude Code

brettstirlingbond and others added 2 commits April 15, 2026 10:51
Build-time funnel graph serialization from frontmatter routing fields.
Generates funnel.json per campaign with nodes/edges, validates for broken
links, orphan pages, missing terminals, asymmetric upsells, and missing
entry points. Includes --lenient flag, dev server error surfacing, and
19 tests at 99.49% coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 98.14241% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/engine/build.js 89.83% 6 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread lib/engine/funnel.js Outdated
// Find nodes that are dead ends (no outgoing edges) but aren't receipts
for (const node of graph.nodes) {
if (node.type === 'receipt') continue;
const outgoing = graph.edges.filter(e => e.source === node.id && nodeIds.has(e.target));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: "Missing terminal" check is incomplete. It only detects nodes with ZERO outgoing edges (outgoing.length === 0), but doesn't verify paths actually reach a receipt. A funnel where checkout→upsell→checkout (cycle) with no receipt would pass validation since neither node has zero outgoing edges.

Comment thread lib/engine/build.js
// were processed and the graph would be incomplete.
const isPartialRebuild = opts.files !== undefined;
let funnelErrors = 0;
for (const [slug, pages] of isPartialRebuild ? [] : campaignPages) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Partial rebuilds skip funnel.json generation entirely. If a developer introduces broken links via frontmatter during dev, the existing funnel.json will be stale (from last full build) until the next full rebuild. Consider regenerating funnel.json even during partial rebuilds, or deleting the stale file.

Comment thread lib/actions/dev.js Outdated
// Initial build
try {
const { built, errors, ms } = await build({ campaigns, mode: 'development' });
const { built, errors, funnelErrors = 0, ms } = await build({ campaigns, mode: 'development' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: dev.js doesn't pass lenient to build(), so funnel validation errors always cause build failures in dev mode. This differs from build.js which supports --lenient. Either add lenient support to dev, or clarify that dev always validates strictly.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented Apr 15, 2026

Code Review Summary

Status: 1 Issue Remaining | Recommendation: Address or document

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
lib/engine/build.js 103 Partial rebuilds leave stale funnel.json (documented but not addressed)
Previously Fixed Issues
File Issue Status
lib/engine/funnel.js Missing terminal check now uses reverse BFS from receipt nodes FIXED
lib/actions/dev.js Dev mode now defaults to lenient, with --strict flag FIXED
lib/engine/funnel.js Duplicate node ID detection added FIXED
lib/engine/funnel-visual.js XSS protection: </script> sequences escaped in JSON embed FIXED (feature removed)
lib/engine/funnel.js urlToNodeId now handles query strings, fragments, and non-http URI schemes FIXED
lib/engine/build.js writeFunnelJson now wrapped in try-catch FIXED
lib/engine/build.js Lenient mode now correctly logs errors before warnings FIXED
lib/actions/build.js / lib/actions/dev.js Refactored to use shared parseFlag and formatBuildSummary FIXED
Files Reviewed (this commit — 9 files)
  • lib/engine/funnel-visual.js - DELETED (373 lines) — HTML visual generation removed; funnel.html no longer produced
  • lib/engine/build.js - removed funnel.html generation; JSON-only output
  • lib/config.js - updated comment to reflect JSON-only output
  • test/funnel.test.js - removed 72 lines of funnel-visual tests
  • docs/funnel-map.md - updated to document JSON-only output with schema reference
  • docs/images/ - screenshots deleted (no longer applicable)
  • README.md - updated to reflect JSON output focus
Other Observations (not in diff)

The HTML visualization feature (funnel.html) was entirely removed in this commit. Users who relied on the browser-based SVG visualization will need to use external graph visualization tools (D3, Cytoscape, Mermaid) with funnel.json. The XSS protection for </script> escaping was tied to this feature and is no longer applicable with JSON-only output.


Reviewed by minimax-m2.7 · 392,825 tokens

- Fix cycle detection in missing-terminal validation: replace dead-end
  check with reverse BFS from receipt nodes so cycles that never reach
  a receipt are caught (funnel.js)
- Dev server defaults to lenient mode (--strict to override) so funnel
  validation errors don't block dev workflow (dev.js)
- Clarify why partial rebuilds skip funnel.json regeneration (build.js)
- Add cycle detection test (funnel.test.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@brettstirlingbond
Copy link
Copy Markdown
Contributor Author

@alexphelps can you please review this branch, its for #10 (comment)

@alexphelps
Copy link
Copy Markdown
Member

@brettstirlingbond

  1. Do you have a prototype of the graph visual? I expect the json structure will need to change meaningfully to make the graph visual work.
  2. Is funnel.json supposed to be public? my gut thinks probably not? All files written to _site/ are deployed to the host server

Overall feeling on this we should get at least an initial v1 of the full "funnel visual graph" integrated to as part of this PR

brettstirlingbond and others added 2 commits April 16, 2026 14:10
…ation

- Move funnel.json from _site/ (deployed) to .cpk/ (developer-only)
- Add opts.cpkPath to build() API, same pattern as opts.outputPath
- Generate self-contained funnel.html with inline SVG visualization
- Color-coded nodes by page_type, edge labels, validation error panel
- Graph data embedded via JSON.stringify with </script> escaping
- Visual generation is try/catch, never blocks the build
- Add duplicate page validation rule (6th rule)
- Layout computed at render time in browser JS, not stored in schema
- 124 tests passing, 99.5% coverage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract parseFlag() and formatBuildSummary() into shared config.js
- Guard urlToNodeId against protocol-relative, mailto:, tel:, and other URI schemes
- Strip query strings and fragments from URLs before node ID resolution
- Wrap writeFunnelJson in try/catch (matching writeFunnelHtml pattern)
- Add partial rebuild debug log for funnel skip reasoning
- Fix lenient mode log order (errors first, then warnings)
- Add 15 new tests covering all new behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@brettstirlingbond brettstirlingbond changed the title feat: add campaign funnel map generation and validation (#10) feat: campaign funnel map — graph, validation, and visual (#10) Apr 16, 2026
@brettstirlingbond
Copy link
Copy Markdown
Contributor Author

@alexphelps, did you want to review this now? I'm going to keep the visual builder very basic for now, as we want to build out the Campaign Toolkit more.

@alexphelps
Copy link
Copy Markdown
Member

@brettstirlingbond Can you provide a guide on how to use this and also update associated guides in the readme for users to use it. I dont see any documentation or screenshots that demonstrate how this feature is supposed to work and validation it works as expected across real world examples.

keep the visual builder very basic for now, as we want to build out the Campaign Toolkit more.

How does this fit into the broader picture with Campaign Tool Kit?

- New docs/funnel-map.md covers all 6 validation rules with example
  errors and fixes, CLI flag behavior, a full 4-page demo, and the
  programmatic API
- New docs/examples/demo-funnel/ — copy-pasteable 4-page campaign
  (product → checkout → upsell → receipt) with minimal layout
- New docs/images/funnel-valid.png and funnel-broken.png — real
  screenshots of the generated .cpk/{slug}/funnel.html
- README.md gains a "Funnel Map" section linking to the guide;
  Commands table now documents --lenient and --strict flags

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@brettstirlingbond
Copy link
Copy Markdown
Contributor Author

@alexphelps documentation added.

"How does this fit into the broader picture with Campaign Tool Kit?"

I'm thinking Campaign Page Kit is more of a developer tool than a marketer's tool. We still need to refine how Marketers interact with the Campaigns hosted on CPK.

@brettstirlingbond
Copy link
Copy Markdown
Contributor Author

@alexphelps anymore feedback on this? Hoping to have this live before we get traffic from TechTopia and HydroNozzle.

@alexphelps
Copy link
Copy Markdown
Member

alexphelps commented Apr 23, 2026

@brettstirlingbond

  1. This should be kept out and is not be a blocker of using CPK in production. CPK is already several steps forward and needs time in production before new features are added.

  2. From my basic testing, this is still incomplete, all our template starter templates have issues for basic straight flows that don't have any complexity yet (and where this would be useful).

  3. Please remove all the example demo funnels from this repo, they wont be maintained and really shouldnt be here.

Example of missing entry warnings that is running against all campaigns even though I chose Olympus to run locally.

[NEXT] WARN  Funnel [olympus-mv-single-step]: Missing entry point: campaign "olympus-mv-single-step" has no index page
[NEXT] INFO  Funnel visual: .cpk/olympus-mv-single-step/funnel.html
[NEXT] WARN  Funnel [demeter]: Missing entry point: campaign "demeter" has no index page
[NEXT] INFO  Funnel visual: .cpk/demeter/funnel.html
[NEXT] WARN  Funnel [limos]: Missing entry point: campaign "limos" has no index page
[NEXT] INFO  Funnel visual: .cpk/limos/funnel.html
[NEXT] WARN  Funnel [shop-three-step]: Missing entry point: campaign "shop-three-step" has no index page
[NEXT] INFO  Funnel visual: .cpk/shop-three-step/funnel.html
[NEXT] WARN  Funnel [olympus]: Missing entry point: campaign "olympus" has no index page
[NEXT] INFO  Funnel visual: .cpk/olympus/funnel.html
[NEXT] WARN  Funnel [shop-single-step]: Missing entry point: campaign "shop-single-step" has no index page
[NEXT] INFO  Funnel visual: .cpk/shop-single-step/funnel.html

Most campaigns I have ever seen don't have an index page, and that's by design warnings above are noise.

Next problem I see is it doesnt support next_page which isn't exactly documented but was previously used to populate the next-page-url meta data for campaign cart would be used to link presale and langing pages to the checkout flow.

Main problem I see at this time is, it doesnt work with even the most basic flows so it's just going to added noise and confusion, questions, and bugs.

This tool will be useful for campaigns with dozens of pages and possible paths, we should build it testing with complex campaign flows so can see all the issues with how the graph visualization handles them.

Per Product Owner direction: pull the HTML/SVG funnel visualization
and keep only funnel.json. Consumers can build their own visual from
the stable JSON schema (D3, Cytoscape, Mermaid, etc.).

- Remove lib/engine/funnel-visual.js (373 lines)
- Remove writeFunnelHtml call from build pipeline
- Remove 3 visual tests (writeFunnelHtml x2, escapeHtml)
- Update build-cpkPath test to assert funnel.html is NOT generated
- Rewrite docs/funnel-map.md to focus on the JSON schema and
  programmatic API; add full schema documentation with field tables
- Update README.md Funnel Map section (drop visual references,
  emphasize JSON consumption)
- Remove docs/images/funnel-*.png screenshots

137 tests pass, 99.3% coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@brettstirlingbond brettstirlingbond changed the title feat: campaign funnel map — graph, validation, and visual (#10) feat: campaign funnel map — JSON graph + validation (#10) Apr 23, 2026
@brettstirlingbond
Copy link
Copy Markdown
Contributor Author

Closing per PO direction — feature kept out for now.

Summary of the feedback that led to this:

  • CPK needs production bake time before new features
  • Validation doesn't fit real campaigns: Missing entry point false-positives on every campaign without an index.html (most don't have one by design)
  • Missing next_page frontmatter support (used for the next-page-url meta tag on presale/landing pages)
  • Runs against all campaigns during dev instead of the selected one
  • Demo funnels don't belong in this repo

Keeping the branch claude/distracted-ardinghelli around in case a future attempt wants a starting point. When we come back to this, the plan is to build + test against complex real campaigns (olympus, demeter, etc.) from day one, not a synthetic 4-page demo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Campaign Funnel Map — serialize campaign page graph to funnel.json

2 participants