Skip to content

feat(website): migrate website app from Fresh/Deno to TanStack Start#17

Merged
JonasJesus42 merged 4 commits intomainfrom
JonasJesus42/migrate-website-app
Apr 13, 2026
Merged

feat(website): migrate website app from Fresh/Deno to TanStack Start#17
JonasJesus42 merged 4 commits intomainfrom
JonasJesus42/migrate-website-app

Conversation

@JonasJesus42
Copy link
Copy Markdown
Contributor

@JonasJesus42 JonasJesus42 commented Apr 13, 2026

Summary

  • Migrates the website app from deco-cx/apps (Fresh/Deno) to @decocms/apps-start (TanStack Start/React/Node), following the existing AppModContract pattern
  • Ports 4 components (Seo, Theme, Analytics, Video) from Preact+Fresh <Head> to React 19 native head hoisting
  • Adds 16 matchers (cookie, device, location, pathname, cron, etc.), 8 flags (audience, everyone, multivariate variants), 5 loaders (fonts, secret, environment), and 3 sections
  • Defines all matcher/flag types locally (MatchContext, Matcher, FlagObj, MultivariateFlag) — no dependency on @deco/deco
  • Includes 97 new tests (222 total pass), covering all matchers, flags, utils, loaders, and the configure function

Test plan

  • tsc --noEmit — zero type errors
  • biome check — zero errors (warnings only, same as other apps)
  • vitest run — 222 tests pass (125 existing + 97 new)
  • Verify Seo/Theme/Analytics components render correctly in a real TanStack Start site
  • Verify matchers evaluate correctly with live request context

🤖 Generated with Claude Code


Summary by cubic

Migrates the website app from Fresh/Deno to TanStack Start/React (@decocms/apps-start) while keeping the AppModContract. Also hardens GTM/GA ID handling, escapes pathname templates, fixes cron weekday 7, improves secret/env loaders, and adds edge‑case tests.

  • New Features

    • React 19 head-hoisted components: Seo, Theme, Analytics, Video
    • 16 matchers, 8 flags (incl. multivariate), 5 loaders (env, secret, fonts), 3 sections (Seo, SeoV2, Analytics)
    • Local matcher/flag types (MatchContext, Matcher, FlagObj, MultivariateFlag) with no @deco/deco dependency
    • App scaffold and generated manifest (website/mod, client, types, index, website/manifest.gen.ts)
    • 97 new tests; 222 total passing, including edge cases and a fix to pass MatchContext to the “Everyone” flag matcher
  • Migration

    • Use @decocms/apps/website/* entry points in TanStack Start apps
    • Run npm run generate:manifests to include the website app
    • Verify Seo, Theme, and Analytics render, and matchers evaluate correctly with live request context

Written for commit 72c8a3d. Summary will update on new commits.

…TanStack Start

Port the website app (~34 modules) from the Fresh/Deno ecosystem to
TanStack Start/React/Node, following the existing AppModContract pattern.

Includes:
- App scaffold (mod.ts, client.ts, types.ts, index.ts)
- 4 components: Seo, Theme, Analytics, Video (Preact → React 19)
- 5 loaders: googleFonts, local fonts, secret, secretString, environment
- 16 matchers: always, never, cookie, cron, date, device, environment,
  host, location, multi, negate, pathname, queryString, random, site, userAgent
- 8 flags: flag, everyone, audience, multivariate (image/message/page/section)
- 3 sections: Seo, SeoV2, Analytics
- 3 utils: html, location, multivariate
- 97 new tests covering all modules (222 total tests pass)

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 55 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="website/components/Analytics.tsx">

<violation number="1" location="website/components/Analytics.tsx:9">
P1: Guard URL parsing in `getGTMIdFromSrc`; malformed `src` currently throws and can break rendering.</violation>

<violation number="2" location="website/components/Analytics.tsx:61">
P1: Escape `trackingId` before injecting into inline GTAG script to avoid script-injection via malformed IDs.</violation>
</file>

<file name="website/matchers/pathname.ts">

<violation number="1" location="website/matchers/pathname.ts:43">
P2: Escape regex metacharacters in template literals before building the `RegExp`; otherwise Template matching can produce false positives for paths containing characters like `.` or `+`.</violation>
</file>

<file name="website/loaders/secret.ts">

<violation number="1" location="website/loaders/secret.ts:32">
P2: Env var resolution incorrectly treats empty-string values as missing due to a truthiness check.</violation>
</file>

<file name="website/utils/html.ts">

<violation number="1" location="website/utils/html.ts:1">
P2: The tag-stripping regex is too broad and will remove non-HTML text between `<` and `>`, causing unintended data loss.</violation>
</file>

<file name="website/matchers/random.ts">

<violation number="1" location="website/matchers/random.ts:17">
P1: `traffic` is treated as a 0-1 probability, but the API/docs describe it as percentage; values like `50` will incorrectly match all traffic.</violation>
</file>

<file name="website/components/Theme.tsx">

<violation number="1" location="website/components/Theme.tsx:33">
P1: Avoid `dangerouslySetInnerHTML` for stylesheet content coming from props; render CSS as text children of `<style>` to prevent HTML/script injection.</violation>
</file>

<file name="website/matchers/cron.ts">

<violation number="1" location="website/matchers/cron.ts:97">
P2: Numeric weekday `7` never matches because parsed DOW values are compared directly to `Date.getDay()` (0-6).</violation>
</file>

<file name="website/matchers/location.ts">

<violation number="1" location="website/matchers/location.ts:58">
P1: Coordinate-based matching incorrectly passes when user coordinates are missing.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread website/components/Analytics.tsx Outdated
Comment thread website/components/Analytics.tsx Outdated
* @icon arrow-split
*/
const MatchRandom = ({ traffic }: Props) => {
return Math.random() < traffic;
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 13, 2026

Choose a reason for hiding this comment

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

P1: traffic is treated as a 0-1 probability, but the API/docs describe it as percentage; values like 50 will incorrectly match all traffic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/matchers/random.ts, line 17:

<comment>`traffic` is treated as a 0-1 probability, but the API/docs describe it as percentage; values like `50` will incorrectly match all traffic.</comment>

<file context>
@@ -0,0 +1,24 @@
+ * @icon arrow-split
+ */
+const MatchRandom = ({ traffic }: Props) => {
+	return Math.random() < traffic;
+};
+
</file context>
Fix with Cubic

<>
{fonts?.map(({ styleSheet }, idx) =>
styleSheet ? (
<style key={idx} type="text/css" dangerouslySetInnerHTML={{ __html: styleSheet }} />
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 13, 2026

Choose a reason for hiding this comment

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

P1: Avoid dangerouslySetInnerHTML for stylesheet content coming from props; render CSS as text children of <style> to prevent HTML/script injection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/components/Theme.tsx, line 33:

<comment>Avoid `dangerouslySetInnerHTML` for stylesheet content coming from props; render CSS as text children of `<style>` to prevent HTML/script injection.</comment>

<file context>
@@ -0,0 +1,47 @@
+		<>
+			{fonts?.map(({ styleSheet }, idx) =>
+				styleSheet ? (
+					<style key={idx} type="text/css" dangerouslySetInnerHTML={{ __html: styleSheet }} />
+				) : null,
+			)}
</file context>
Fix with Cubic

}
let result = !target.regionCode || target.regionCode === source.regionCode;
result &&=
!source.coordinates ||
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 13, 2026

Choose a reason for hiding this comment

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

P1: Coordinate-based matching incorrectly passes when user coordinates are missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/matchers/location.ts, line 58:

<comment>Coordinate-based matching incorrectly passes when user coordinates are missing.</comment>

<file context>
@@ -0,0 +1,113 @@
+		}
+		let result = !target.regionCode || target.regionCode === source.regionCode;
+		result &&=
+			!source.coordinates ||
+			!target.coordinates ||
+			haversine(source.coordinates, target.coordinates) <= Number(target.coordinates.split(",")[2]);
</file context>
Fix with Cubic

Comment thread website/matchers/pathname.ts Outdated
Comment thread website/loaders/secret.ts Outdated
Comment thread website/utils/html.ts
@@ -0,0 +1 @@
export const stripHTML = (str: string) => str.replace(/(<([^>]+)>)/gi, "");
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 13, 2026

Choose a reason for hiding this comment

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

P2: The tag-stripping regex is too broad and will remove non-HTML text between < and >, causing unintended data loss.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At website/utils/html.ts, line 1:

<comment>The tag-stripping regex is too broad and will remove non-HTML text between `<` and `>`, causing unintended data loss.</comment>

<file context>
@@ -0,0 +1 @@
+export const stripHTML = (str: string) => str.replace(/(<([^>]+)>)/gi, "");
</file context>
Fix with Cubic

Comment thread website/matchers/cron.ts
JonasJesus42 and others added 3 commits April 13, 2026 16:00
Remove unused variables in cron matcher, fix parseInt radix,
auto-fix import ordering and formatting across website tests
and manifests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard URL parsing in getGTMIdFromSrc with try/catch
- Sanitize trackingId in GTAG to prevent script injection
- Escape regex metacharacters in pathname Template matcher
- Fix empty-string env var treated as missing in secret loader
- Handle cron weekday 7 as alias for Sunday (0)
- Add tests for new edge cases

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@JonasJesus42 JonasJesus42 merged commit 2a75f16 into main Apr 13, 2026
2 checks passed
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.3.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant