Skip to content

Conversation

@guitavano
Copy link
Contributor

@guitavano guitavano commented Jun 5, 2025

What is this Contribution About?

Please provide a brief description of the changes or enhancements you are proposing in this pull request.

Issue Link

Please link to the relevant issue that this pull request addresses:

Loom Video

Record a quick screencast describing your changes to help the team understand and review your contribution. This will greatly assist in the review process.

Demonstration Link

Provide a link to a branch or environment where this pull request can be tested and seen in action.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced event tracking system with automatic pageview detection on navigation and comprehensive event collection integration.
  • Refactor

    • Optimized GraphQL client with query minification and dynamic HTTP method selection for improved performance.
    • Enhanced URL routing with improved pattern matching and ranking for dynamic path segments.

aka-sacci-ccr and others added 30 commits June 12, 2024 16:18
@github-actions
Copy link
Contributor

github-actions bot commented Jun 5, 2025

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 0.100.5 update
  • 🎉 for Minor 0.101.0 update
  • 🚀 for Major 1.0.0 update

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 23, 2025

Walkthrough

The PR refactors GraphQL client request handling with improved method selection and parameter management, introduces a comprehensive pageview and event tracking system with event subscriptions, adds a new URL slug extraction loader for request routing, enhances route ranking logic for optional and wildcard segments, and updates the manifest accordingly.

Changes

Cohort / File(s) Summary
GraphQL client refactoring
utils/graphql.ts
Made GraphQL API payload body optional. Added GraphQLQueryProps interface for query encapsulation. Introduced internal helpers (getMethodAndProps, getParamsAndBody, minifyString, joinQueryArgs, stringfyVariables, isPostMethodRequired) for dynamic request method selection and parameter construction. Updated createGraphqlClient to use object destructuring and rely on helper logic. Added DecoRequestInit import.
Analytics tracking system
website/components/OneDollarStats.tsx
Exported trackerOriginal constant with full tracking script. Expanded runtime wiring with pageview tracking via monkey-patched history.pushState and popstate events. Added data truncation, event subscription mechanisms for flag/identifier accumulation, and named event forwarding to window.stonks.event. Enhanced PageView payload emission with utm data and path resolution.
Request routing enhancements
website/functions/requestToPathname.ts, website/handlers/router.ts
Added new requestToPathname loader function that extracts URL slugs by removing configured pathname prefixes. Exports Props interface. Modified router.ts route ranking to adjust scores for optional/wildcard segments. Rewrote URL pattern construction to use URLPattern directly and derive pathname/search components with defaults.
Manifest updates
website/manifest.gen.ts
Added import alias for requestToPathname module and wired new function entry into manifest.functions mapping.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant createGraphqlClient
    participant getMethodAndProps
    participant getParamsAndBody
    participant HTTPClient

    Client->>createGraphqlClient: query(GraphQLQueryProps, DecoRequestInit)
    createGraphqlClient->>getMethodAndProps: Analyze query & fragments
    getMethodAndProps-->>createGraphqlClient: Return {method, props}
    
    alt Method = GET
        createGraphqlClient->>getParamsAndBody: Build GET params
        getParamsAndBody->>getParamsAndBody: Minify & compress query
        getParamsAndBody-->>createGraphqlClient: searchParams
        createGraphqlClient->>HTTPClient: GET with searchParams
    else Method = POST
        createGraphqlClient->>getParamsAndBody: Build POST body
        getParamsAndBody-->>createGraphqlClient: {query, variables, operationName}
        createGraphqlClient->>HTTPClient: POST with body
    end
    
    HTTPClient-->>Client: Response
Loading
sequenceDiagram
    participant Browser
    participant DecoEvents
    participant TrackerScript
    participant Analytics
    
    Browser->>Browser: Load page / Navigate (pushState/popstate)
    Browser->>TrackerScript: History event triggered
    TrackerScript->>TrackerScript: Capture navigation data
    
    DecoEvents->>TrackerScript: Emit deco:* events
    TrackerScript->>TrackerScript: Accumulate flags & identifiers
    
    TrackerScript->>TrackerScript: Construct PageView payload
    Note over TrackerScript: {url, utm_*, ...props}
    TrackerScript->>Analytics: window.stonks.pageView(payload)
    
    DecoEvents->>TrackerScript: Generic event data
    TrackerScript->>Analytics: window.stonks.event(name, values)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes span multiple systems with varying complexity: the GraphQL refactor introduces multiple internal helpers with non-trivial logic for request method selection and minification; the tracking system adds event subscriptions and browser API monkey-patching; route ranking logic requires careful review of edge cases; and the new loader integrates with the router. While changes are relatively well-scoped per file, the collective scope and behavioral significance across systems warrants thorough review.

Suggested reviewers

  • mcandeia

Poem

🐰 Whiskers twitch with graphql cheer,
GET and POST requests crystal clear,
Tracking hops through history's dance,
Routes ranked with wild circumstance,
Deco's loaders bound with care—
Such clever code! We hop, we share!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description is entirely unfilled and contains only the template structure with no actual content. All required sections—"What is this Contribution About?", "Issue Link", "Loom Video", and "Demonstration Link"—are missing or show only placeholder text with no substantive information, issue references, videos, or demonstration links provided. Complete the pull request description by filling in all required sections: provide a clear summary of the GraphQL refactoring, tracking improvements, and routing changes; link to the related issue; include a Loom video demonstrating the changes; and provide a demonstration link where the PR can be tested.
Title Check ❓ Inconclusive The title "Fix magento" is vague and generic without conveying meaningful information about the actual changes. The PR makes significant modifications including GraphQL API refactoring with new helper functions and type changes, enhancements to tracking functionality in OneDollarStats, addition of a new requestToPathname loader function, and updates to route ranking logic. None of these specific changes are reflected in the overly broad title, making it unclear to a teammate scanning history what the primary objectives are.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-magento

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (4)
website/components/OneDollarStats.tsx (1)

30-33: Exporting full tracker script risks bundle bloat and config drift.

Consider moving trackerOriginal to a separate asset/module or guarding behind dev-only export; it hardcodes https://collector.onedollarstats.com/events while the component uses DEFAULT_COLLECTOR_ADDRESS (https://d.lilstts.com/events). This can confuse consumers if imported.

utils/graphql.ts (3)

159-162: stringfyVariables returns "undefined" for empty input; rename and make optional.

Return undefined when no variables to keep URLs short, and fix spelling.

Apply:

-const stringfyVariables = <V>({
-  variables,
-}: Pick<GraphQLQueryProps<V>, "variables">) => JSON.stringify(variables);
+const stringifyVariables = <V>({
+  variables,
+}: Pick<GraphQLQueryProps<V>, "variables">) =>
+  variables === undefined ? undefined : JSON.stringify(variables);

And update callers:

-  const stringfiedVariables = stringfyVariables<V>({ variables });
+  const stringfiedVariables = stringifyVariables<V>({ variables });

163-179: Use encodeURIComponent and include operationName consistently.

For length estimation, prefer encodeURIComponent and only append operationName when present to avoid inflating length.

-  const urlLength = `${href}?query=${encodeURI(query)}&variables=${
-    encodeURI(
-      variables,
-    )
-  }&operationname=${operationName}`;
+  const qp = [
+    `query=${encodeURIComponent(query)}`,
+    `variables=${encodeURIComponent(variables ?? "")}`,
+    operationName ? `operationName=${encodeURIComponent(operationName)}` : "",
+  ].filter(Boolean).join("&");
+  const urlLength = `${href}?${qp}`;

84-124: Guard against GET with huge fragments arrays.

If fragments is large, isPostMethodRequired may still choose GET before minification is applied to all parts. Consider estimating with the joined+minified query to avoid overly long URLs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1ae949 and d7ce899.

📒 Files selected for processing (5)
  • utils/graphql.ts (4 hunks)
  • website/components/OneDollarStats.tsx (1 hunks)
  • website/functions/requestToPathname.ts (1 hunks)
  • website/handlers/router.ts (3 hunks)
  • website/manifest.gen.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
website/functions/requestToPathname.ts (1)
website/functions/requestToParam.ts (1)
  • RequestURLParam (3-3)
utils/graphql.ts (1)
utils/fetch.ts (1)
  • DecoRequestInit (34-36)
🔇 Additional comments (4)
website/manifest.gen.ts (1)

15-16: Manifest wiring looks correct.

Import and mapping for website/functions/requestToPathname.ts added alongside requestToParam. No action needed.

Also applies to: 64-65

utils/graphql.ts (2)

16-26: Type change to optional body in GraphQLAPI is fine.

This aligns with GET support; no issues spotted.


137-151: Minifier correctly handles nested selections; no changes required.

The test confirms your minifier produces correct output for nested GraphQL selections. The outer braces { checkout(...) { ...Checkout } } are not matched by the first regex (which explicitly requires [^{}]*—no nested braces), but the subsequent whitespace-stripping replacements handle nesting correctly. The minified output is valid: query GetCart($checkoutId:String!){checkout(checkoutId:$checkoutId){...Checkout}}. No fix or test addition is necessary.

website/handlers/router.ts (1)

88-99: I need to verify the URLPattern API specification to understand how the search property should be formatted.

Let me search for more specific information about how URLPattern handles the search property when constructed with a base URL.

Let me search for more specific information about what the .search property returns when a URLPattern is constructed from a URL string without a query component.

Based on my verification, I can now provide the final response.

No issues found with the URLPattern search component formatting.

The URLPattern search property is a string containing the pattern used to match the search part of a URL, and is the normalized value of the search pattern passed to the constructor or the default value ("*"), which matches any search part. When using search in a URLPattern constructor object, the value does not include the leading question mark—for example, const pattern = new URLPattern({ search: "q=baby" }) logs pattern.search as "q=baby".

The code correctly handles both cases: when patternWithDefaultOrigin.search is an empty string (from a URL with no query component), the fallback to "*" maintains consistent formatting since neither value includes a leading ?.

Comment on lines 53 to 74
return {
query: async <D, V>(
{ query = "", fragments = [], variables, operationName }: {
query: string;
fragments?: string[];
variables?: V;
operationName?: string;
},
init?: RequestInit,
{
query = "",
fragments = [],
variables,
operationName,
}: GraphQLQueryProps<V>,
init?: DecoRequestInit,
): Promise<D> => {
const { data, errors } = await http[key as any]({}, {
...init,
body: {
query: [query, ...fragments].join("\n"),
variables: variables as any,
operationName,
},
const { key, props } = getMethodAndProps<V>({
query,
fragments,
variables,
operationName,
url,
});
const { searchParams, body } = getParamsAndBody({ key, props, init });
const { data, errors } = await http[key as any](searchParams, {
...body,
}).then((res) => res.json());

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

POST body may not be JSON‑stringified; init.body can override payload.

  • getParamsAndBody returns { body: props, ...init } for POST; if createHttpClient doesn’t stringify, requests may send [object Object] or fail.
  • Spreading ...init after body: props can replace the computed body.

Ensure JSON string body and keep props as the final body.

Apply:

-      const { searchParams, body } = getParamsAndBody({ key, props, init });
-      const { data, errors } = await http[key as any](searchParams, {
-        ...body,
-      }).then((res) => res.json());
+      const { searchParams, init: httpInit } = getParamsAndBody({ key, props, init });
+      const { data, errors } = await http[key as any](searchParams, httpInit)
+        .then((res) => res.json());

And update helper (see next comment).

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +126 to +135
const getParamsAndBody = ({
key,
props,
init,
}: ReturnType<typeof getMethodAndProps> & { init?: DecoRequestInit }) => {
if (key.startsWith("POST")) {
return { searchParams: {}, body: { body: props, ...init } };
}
return { searchParams: { ...props }, body: { ...init } };
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Harden GET/POST param packing and avoid undefined values.

  • For POST, JSON‑stringify the payload and ensure it wins over init.body.
  • For GET, omit keys with undefined to avoid variables=undefined in the URL.

Apply:

-const getParamsAndBody = ({
+const getParamsAndBody = ({
   key,
   props,
   init,
 }: ReturnType<typeof getMethodAndProps> & { init?: DecoRequestInit }) => {
   if (key.startsWith("POST")) {
-    return { searchParams: {}, body: { body: props, ...init } };
+    const bodyJson = JSON.stringify(props);
+    return { searchParams: {}, init: { ...(init ?? {}), body: bodyJson } };
   }
-  return { searchParams: { ...props }, body: { ...init } };
+  const searchParams: Record<string, string> = {};
+  if (props.query) searchParams.query = props.query;
+  if (props.operationName) searchParams.operationName = props.operationName;
+  if (typeof props.variables === "string" && props.variables.length > 0) {
+    searchParams.variables = props.variables;
+  }
+  return { searchParams, init: { ...(init ?? {}) } };
 };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In utils/graphql.ts around lines 126 to 135, harden the GET/POST param packing
by: for POST requests ensure the request body is a JSON string of props
(JSON.stringify(props)) and that this serialized payload takes precedence over
any init.body (i.e., set body: { body: JSON.stringify(props), ...init } but
ensure init.body is not used if props provided); for GET requests build
searchParams by copying props but filtering out keys whose values are undefined
so you don't produce query entries like variables=undefined (omit
undefined-valued keys before returning searchParams); keep the same return shape
({ searchParams, body }) and preserve other init fields.

Comment on lines +4 to +10
export interface Props {
/**
* @description Path name to remove from the URL - Stringfied regex. Example: "/product/" transform "/product/my-sku" in "my-sku". "/product/|/store" transform "/product/my-sku/store" in "my-sku"
* @format dynamic-options
*/
pathname?: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc nit: typo and clarify examples.

“Stringfied” → “Stringified”. Consider clarifying anchoring (prefix-only vs anywhere) in examples to avoid accidental middle replacements.

🤖 Prompt for AI Agents
In website/functions/requestToPathname.ts around lines 4 to 10, fix the
documentation typo ("Stringfied" → "Stringified") and clarify the examples to
state whether the provided string is treated as a full/anchored pattern or can
match anywhere; update the examples to explicitly show anchoring behavior (e.g.,
prefix-only vs anywhere) and mention that the value is a stringified regex so
callers should include anchors (or that the code will add anchors) to avoid
accidental middle replacements.

Comment on lines +16 to +26
const requestToParam: LoaderFunction<
Props,
RequestURLParam,
FunctionContext
> = (req, ctx) => {
const url = new URL(req.url);
const regex = new RegExp("(" + ctx.state.$live.pathname + ")", "g");
return {
data: url.pathname.toString().replace(regex, ""),
};
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Props ignored and regex can over‑match; anchor and escape.

  • Props.pathname is never used; current code relies on ctx.state.$live.pathname.
  • The regex isn’t anchored and may strip matches anywhere in the path; unescaped input can behave unexpectedly.

Use props?.pathname ?? ctx.state.$live.pathname, anchor to start, and fall back safely.

Apply:

-const requestToParam: LoaderFunction<
-  Props,
-  RequestURLParam,
-  FunctionContext
-> = (req, ctx) => {
-  const url = new URL(req.url);
-  const regex = new RegExp("(" + ctx.state.$live.pathname + ")", "g");
-  return {
-    data: url.pathname.toString().replace(regex, ""),
-  };
-};
+const requestToParam: LoaderFunction<
+  Props,
+  RequestURLParam,
+  FunctionContext
+> = (req, ctx, props) => {
+  const url = new URL(req.url);
+  const pattern = (props?.pathname ?? ctx.state.$live.pathname ?? "").trim();
+  if (!pattern) {
+    // strip leading slash only
+    return { data: url.pathname.replace(/^\/+/, "") };
+  }
+  // Anchor to start. If you intend a true regex, keep as-is; otherwise escape:
+  // const safe = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
+  const regex = new RegExp(`^(?:${pattern})`);
+  const slug = url.pathname.replace(regex, "").replace(/^\/+/, "");
+  return { data: slug };
+};
🤖 Prompt for AI Agents
In website/functions/requestToPathname.ts around lines 16 to 26, the function
ignores the passed-in props and builds an unescaped, unanchored regex from
ctx.state.$live.pathname which can over-match; instead choose the basePath as
props?.pathname ?? ctx.state.$live.pathname, escape any regex metacharacters in
that value, anchor it to the start of the pathname (prepend ^) so only a leading
match is removed, and use a single replace (not a global replace) to strip the
basePath; ensure you coerce the chosen pathname to a string and handle
null/undefined safely.

Comment on lines +29 to +34
if (routePart.startsWith("}?")) {
if (routePart.includes("*")) {
return acc - 2;
}
return acc - 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Validate rank logic for optional/wildcard segments starting with }?.

Ensure your route syntax can actually produce segments that begin with "}?". If not, this branch never triggers or could mask other cases. Add a unit test with representative patterns to confirm ordering.


🏁 Script executed:

#!/bin/bash
# List route templates that contain '}' at segment start
rg -nP '(^|/)}\?' --type=ts -C2 website | sed -n '1,200p'

Length of output: 0


I need to better understand the route template syntax and how segments are constructed. Let me examine the router.ts file and search for test cases or route examples.


🏁 Script executed:

#!/bin/bash
# Read the router.ts file to understand context
cat website/handlers/router.ts

Length of output: 6575


Let me search for test files and route definition examples to understand what route patterns actually exist:


🏁 Script executed:

#!/bin/bash
# Search for test files that might contain route examples
fd -e test.ts -e test.tsx -e _test.ts -o -name '*test*.ts' website | head -20

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Look for route definitions and examples
rg -n "pathTemplate" --type=ts website | head -30

Length of output: 2415


🏁 Script executed:

#!/bin/bash
# Search for what patterns actually contain } character
rg -nP '\}' --type=ts website/handlers/router.ts -C3

Length of output: 6499


🏁 Script executed:

#!/bin/bash
# Look at flags/audience.ts to understand route structure
cat website/flags/audience.ts 2>/dev/null || echo "File not found"

Length of output: 1879


Let me search more broadly for any route patterns that might explain the }? syntax, and check URLPattern usage:


🏁 Script executed:

#!/bin/bash
# Search for actual values assigned to pathTemplate
rg -nP 'pathTemplate\s*[=:]' --type=ts -A1 website | head -50

Length of output: 2659


🏁 Script executed:

#!/bin/bash
# Look for any patterns with { or } characters that might be encoded or escaped
rg -nP '[{}<>]' --type=ts website/loaders/options/routes.ts -C2

Length of output: 814


🏁 Script executed:

#!/bin/bash
# Search for imports or usage of URLPattern to understand the syntax
rg -n "URLPattern\|urlPatternCache" --type=ts website -C2 | head -60

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there are any comments about route syntax or rankRoute logic
rg -n "rankRoute\|route.*syntax\|pattern.*format" --type=ts website -C3

Length of output: 0


Let me search for any test files related to routing to confirm there's no test coverage:


🏁 Script executed:

#!/bin/bash
# Search for any test or spec files related to routing
find website -type f \( -name "*.test.ts" -o -name "*.spec.ts" -o -name "*_test.ts" \) | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for rankRoute function usage and calls
rg -n "rankRoute" --type=ts website

Length of output: 199


🏁 Script executed:

#!/bin/bash
# Check if URLPattern is used and what patterns are created
grep -n "URLPattern\|new URLPattern" website/handlers/router.ts

Length of output: 200


Remove dead code from rankRoute or add tests demonstrating when }? pattern can occur.

The }? check in the rankRoute function (line 29) appears unreachable: no route patterns in the codebase use this syntax, and it doesn't match standard URLPattern Web API syntax (which uses :param, *, **). Either remove this branch or add unit tests with concrete route examples that would trigger it.

🤖 Prompt for AI Agents
In website/handlers/router.ts around lines 29 to 34, the branch checking for
routePart.startsWith("}?") appears dead/unreachable; either remove this branch
to simplify rankRoute or add unit tests demonstrating a real route pattern using
"}?". Fix by choosing one: (A) delete the entire if (routePart.startsWith("}?"))
{ ... } block and adjust any dependent logic/tests accordingly, or (B) keep it
but add unit tests with concrete route strings that include the "}?"/wildcard
combinations (including cases with and without "*") that assert the decrement
behavior (acc -2 vs acc -1) so the branch is covered; ensure tests reference
existing route parsing expectations and update comments to explain why the
pattern is supported.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
website/components/OneDollarStats.tsx (2)

51-66: Fix: accidental invocation of subscribe return; add DECO guard; null‑safe page.id

Calling the return value of subscribe (})();) will throw at runtime. Also, window.DECO may not exist yet, and page.id is accessed without a null check.

Apply:

-  globalThis.window.DECO.events.subscribe((event) => {
+  if (globalThis.window.DECO?.events?.subscribe) {
+    globalThis.window.DECO.events.subscribe((event) => {
       if (!event || event.name !== "deco") {
         return;
       }
-      if (event.params) {
-        const { flags, page } = event.params;
+      if (event.params) {
+        const { flags, page } = event.params;
         if (Array.isArray(flags)) {
           for (const flag of flags) {
             props[flag.name] = truncate(flag.value.toString());
           }
         }
-        props["pageId"] = truncate(`${page.id}`);
+        if (page?.id != null) {
+          props["pageId"] = truncate(String(page.id));
+        }
       }
       trackPageview();
-  })();
+    });
+  }

34-47: Prevent duplicate PageView emissions from pushState/popstate + "deco"

Confirmed: website/components/OneDollarStats.tsx both monkey‑patches history.pushState / listens to popstate and also uses the DECO "deco" subscription (subscribe(...)()) to call trackPageview — this can double‑count pageviews on SPA navigations.

Minimal dedupe (skip repeated same href within a short window):

-  const trackPageview = () => globalThis.window.stonks?.view?.(props);
+  let lastHref = "";
+  let lastTs = 0;
+  const trackPageview = () => {
+    const href = location.href.replace(/#.*$/, "");
+    const now = Date.now();
+    if (href === lastHref && now - lastTs < 500) return;
+    lastHref = href; lastTs = now;
+    globalThis.window.stonks?.view?.(props);
+  };

Or remove the pushState/popstate hooks and rely only on the DECO event if that event is guaranteed for all client-side navigations.

🧹 Nitpick comments (6)
website/components/OneDollarStats.tsx (6)

48-50: Truncation says “2000 bytes” but slices 990 characters

Mismatch and not byte‑safe for multi‑byte chars.

Byte‑aware truncation:

-  // 2000 bytes limit
-  const truncate = (str: string) => `${str}`.slice(0, 990);
+  // 2000 bytes limit (UTF‑8)
+  const truncate = (val: unknown, maxBytes = 2000) => {
+    const s = typeof val === "string" ? val : String(val ?? "");
+    const enc = new TextEncoder();
+    if (enc.encode(s).length <= maxBytes) return s;
+    let lo = 0, hi = s.length;
+    while (lo < hi) {
+      const mid = (lo + hi) >> 1;
+      (enc.encode(s.slice(0, mid)).length <= maxBytes) ? lo = mid + 1 : hi = mid;
+    }
+    return s.slice(0, Math.max(0, lo - 1));
+  };

75-84: Use Object.entries to avoid inherited keys and TS ignore

for..in can traverse prototype properties; also avoids the TS ignore.

-    for (const key in params) {
-      // @ts-expect-error somehow typescript bugs
-      const value = params[key];
+    for (const [key, value] of Object.entries(params)) {
       if (value !== null && value !== undefined) {
-        values[key] = truncate(
-          typeof value !== "object" ? value : JSON.stringify(value),
-        );
+        values[key] = truncate(
+          typeof value !== "object" ? value : JSON.stringify(value),
+        );
       }
     }

97-101: Preconnect/dns-prefetch should target origin, not path; encode staticScript URL

Using /events path for preconnect/dns‑prefetch is suboptimal; also query param should be URL‑encoded.

 function Component({ collectorAddress, staticScriptUrl }: Props) {
   const collector = collectorAddress ?? DEFAULT_COLLECTOR_ADDRESS;
   const staticScript = staticScriptUrl ?? DEFAULT_ANALYTICS_SCRIPT_URL;
+  const collectorOrigin = (() => { try { return new URL(collector).origin; } catch { return collector; } })();

   return (
     <Head>
-      <link rel="dns-prefetch" href={collector} />
+      <link rel="dns-prefetch" href={collectorOrigin} />
       <link
         rel="preconnect"
-        href={collector}
+        href={collectorOrigin}
         crossOrigin="anonymous"
       />
       <script
         id="tracker"
         data-autocollect="false"
         data-hash-routing="true"
         data-url={collector}
         type="module"
-        src={`/live/invoke/website/loaders/analyticsScript.ts?url=${staticScript}`}
+        src={`/live/invoke/website/loaders/analyticsScript.ts?url=${encodeURIComponent(staticScript)}`}
       />

Also applies to: 109-109


16-28: Add Window.DECO typing

You declared Window.stonks but not Window.DECO, causing TS friction.

 declare global {
   interface Window {
     stonks: {
       event: (
         name: string,
         params: Record<string, string | boolean>,
       ) => void;
       view: (
         params: Record<string, string | boolean>,
       ) => void;
     };
+    DECO?: {
+      events: {
+        subscribe: (fn: (event: unknown) => unknown) => unknown;
+      };
+    };
   }
 }

35-35: Typo

“dimentions” → “dimensions”.

-  // Flags and additional dimentions
+  // Flags and additional dimensions

30-33: Exported trackerOriginal is unused here

If it’s intentional for external import, keep it. If not, remove to reduce bundle size.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7ce899 and 10da010.

📒 Files selected for processing (5)
  • google-drive/utils/query.ts (1 hunks)
  • google-drive/utils/types.ts (3 hunks)
  • hubspot/actions/conversations/archiveThread.ts (0 hunks)
  • hubspot/actions/conversations/updateThread.ts (0 hunks)
  • website/components/OneDollarStats.tsx (1 hunks)
💤 Files with no reviewable changes (2)
  • hubspot/actions/conversations/updateThread.ts
  • hubspot/actions/conversations/archiveThread.ts
✅ Files skipped from review due to trivial changes (2)
  • google-drive/utils/query.ts
  • google-drive/utils/types.ts
🔇 Additional comments (1)
website/components/OneDollarStats.tsx (1)

37-47: Order-of-execution note

Ensure the module tracker script initializes window.stonks before oneDollarSnippet runs. You’re using two deferred scripts; order in DOM should preserve execution, but race is still possible on slow networks.

If races occur, gate trackPageview() calls until window.stonks is truthy, or buffer events.

Also applies to: 51-66

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
website/components/OneDollarStats.tsx (1)

51-65: Fix subscription invocation bug.

Line 65 ends with })(); which immediately invokes the return value of subscribe(). If subscribe() returns an unsubscribe function (common pattern), this would immediately unsubscribe the handler, preventing it from receiving any events. This is inconsistent with the second subscription at line 86, which correctly doesn't invoke the return value.

Apply this diff to fix the issue:

-  })();
+  });
🧹 Nitpick comments (2)
website/components/OneDollarStats.tsx (2)

48-49: Clarify the truncation limit.

The comment mentions a 2000 bytes limit, but the truncate function uses 990 characters. Consider documenting why this specific value was chosen (e.g., accounting for multiple fields, safety margin, etc.).


30-32: Remove unused export trackerOriginal.

The trackerOriginal constant is exported but never used anywhere in the codebase. Remove this dead code to reduce bundle size.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10da010 and dd97fc1.

📒 Files selected for processing (1)
  • website/components/OneDollarStats.tsx (1 hunks)
🔇 Additional comments (1)
website/components/OneDollarStats.tsx (1)

67-86: Event forwarding logic looks correct.

The subscription correctly filters and forwards non-"deco" events to window.stonks.event, merging accumulated props with event params. The truncation and serialization logic appropriately handles different value types.

Note: The @ts-expect-error on line 78 is acceptable given the dynamic nature of event parameters, though consider adding a more specific comment explaining why the type assertion is needed.

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.

10 participants