feat: create related posts and products#1584
Conversation
📝 WalkthroughWalkthroughAdds dynamic-options JSDoc/metadata to ChangesBlog types metadata and Template fix
Possibly Related PRs
Suggested Reviewers
Poem
Estimated Code Review Effort🎯 2 (Simple) | ⏱️ ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Tagging OptionsShould a new tag be published when this PR is merged?
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@blog/sections/Template.tsx`:
- Around line 55-58: The JSX currently injects raw HTML via
dangerouslySetInnerHTML using the content variable in Template.tsx; import the
existing sanitizeHtml utility from spire/utils/sanitizeHtml.ts and run content
through it before passing into dangerouslySetInnerHTML (e.g., const safeHtml =
sanitizeHtml(content as string)), then use safeHtml in the __html field to strip
scripts, event handlers and dangerous protocols and eliminate the XSS risk.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d70eea3f-51e9-407b-9c3d-5edb8d0b64ea
📒 Files selected for processing (2)
blog/sections/Template.tsxblog/types.ts
| <div | ||
| class="prose max-w-none mb-10" | ||
| dangerouslySetInnerHTML={{ __html: content as string }} | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether HTML content is sanitized before render.
# 1) Find all raw HTML injection points.
rg -n -C3 'dangerouslySetInnerHTML' --type=ts --type=tsx
# 2) Find sanitization utilities/usages in the blog rendering pipeline.
rg -n -C3 'sanitize|sanitiz|DOMPurify|xss|rehype-sanitize|sanitize-html' --type=ts --type=tsx
# 3) Trace where blog post content is loaded/transformed before Template.
rg -n -C3 '\bcontent\b' blog --type=ts --type=tsxRepository: deco-cx/apps
Length of output: 140
🏁 Script executed:
#!/bin/bash
# 1) Find all raw HTML injection points in blog with correct file pattern
echo "=== Searching for dangerouslySetInnerHTML ==="
rg -n -C3 'dangerouslySetInnerHTML' -g '*.tsx' -g '*.ts' blog/
# 2) Find sanitization utilities in the entire blog directory
echo "=== Searching for sanitization patterns ==="
rg -n 'sanitize|DOMPurify|xss|rehype-sanitize|sanitize-html' -g '*.tsx' -g '*.ts' blog/
# 3) Search for imports/requires in Template.tsx specifically
echo "=== Template.tsx imports ==="
head -50 blog/sections/Template.tsxRepository: deco-cx/apps
Length of output: 2246
🏁 Script executed:
#!/bin/bash
# Find the loader function and BlogPost type definition
echo "=== BlogPost type definition ==="
cat blog/types.ts
echo ""
echo "=== Searching for loader function in Template.tsx ==="
rg -n 'loader|export' blog/sections/Template.tsx
echo ""
echo "=== Looking for loader file in blog directory ==="
find blog -name '*loader*' -o -name '*load*' 2>/dev/null | head -20
echo ""
echo "=== Check blog directory structure ==="
ls -la blog/Repository: deco-cx/apps
Length of output: 6561
🏁 Script executed:
#!/bin/bash
# Read the loader function from Template.tsx
echo "=== Loader function from Template.tsx ==="
sed -n '81,130p' blog/sections/Template.tsx
echo ""
echo "=== Check loaders directory ==="
ls -la blog/loaders/
echo ""
echo "=== Search for rich-text or content processing ==="
rg -n 'rich-text|content.*sanitize|sanitize.*content' -g '*.ts' -g '*.tsx' blog/Repository: deco-cx/apps
Length of output: 1108
🏁 Script executed:
#!/bin/bash
# Check mod.ts and other framework setup
echo "=== mod.ts (framework setup) ==="
cat blog/mod.ts
echo ""
echo "=== Search for other rich-text format usages ==="
rg -n '@format rich-text' -g '*.ts' -g '*.tsx'
echo ""
echo "=== Check if Deco has sanitization in dependencies ==="
find . -name 'deno.json' -o -name 'deno.lock' -o -name 'package.json' 2>/dev/null | head -5Repository: deco-cx/apps
Length of output: 1283
🏁 Script executed:
#!/bin/bash
# Check deno.json for `@deco/deco` and any sanitization dependencies
echo "=== deno.json (first 100 lines) ==="
head -100 deno.json
echo ""
echo "=== admin/widgets.ts rich-text context ==="
rg -n -B5 -A5 '@format rich-text' admin/widgets.tsRepository: deco-cx/apps
Length of output: 3139
🏁 Script executed:
#!/bin/bash
# Final verification: search for any comments about content safety
echo "=== Search for content safety comments/docs ==="
rg -n -i 'sanitize|xss|danger|safe|trust|content.*html' -g '*.ts' -g '*.tsx' blog/ | head -30
echo ""
echo "=== Check if similar patterns exist elsewhere ==="
rg -n 'dangerouslySetInnerHTML' -g '*.tsx' -g '*.ts' . --max-count=10Repository: deco-cx/apps
Length of output: 4359
🏁 Script executed:
#!/bin/bash
# Critical: Found that spire app DOES sanitize. Let's find the sanitizeHtml function
echo "=== Finding sanitizeHtml implementation ==="
rg -n 'function sanitizeHtml|export.*sanitizeHtml|const sanitizeHtml' -g '*.ts' -g '*.tsx'
echo ""
echo "=== Check spire app's sanitization imports ==="
head -30 spire/sections/blocks/Paragraph.tsxRepository: deco-cx/apps
Length of output: 779
🏁 Script executed:
#!/bin/bash
# Check the sanitizeHtml implementation
echo "=== sanitizeHtml implementation ==="
cat spire/utils/sanitizeHtml.ts
echo ""
echo "=== Check if blog app imports or uses sanitizeHtml ==="
rg -n 'sanitizeHtml' blog/Repository: deco-cx/apps
Length of output: 1468
Sanitize content before dangerouslySetInnerHTML (Line 57).
Rendering post.content directly here is an XSS risk. The content comes from the CMS (@format rich-text) and is injected without sanitization. A sanitizeHtml utility already exists in this codebase at spire/utils/sanitizeHtml.ts that can be reused to strip XSS vectors (scripts, event handlers, dangerous protocols).
🧰 Tools
🪛 ast-grep (0.42.2)
[warning] 56-56: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@blog/sections/Template.tsx` around lines 55 - 58, The JSX currently injects
raw HTML via dangerouslySetInnerHTML using the content variable in Template.tsx;
import the existing sanitizeHtml utility from spire/utils/sanitizeHtml.ts and
run content through it before passing into dangerouslySetInnerHTML (e.g., const
safeHtml = sanitizeHtml(content as string)), then use safeHtml in the __html
field to strip scripts, event handlers and dangerous protocols and eliminate the
XSS risk.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@blog/types.ts`:
- Line 4: The BlogPost type is missing the relatedProducts field though Product
is imported; update the BlogPost interface to include a relatedProducts property
(e.g., relatedProducts?: Product[] or relatedProducts: Product[] as appropriate
for optionality) alongside the existing relatedPosts to satisfy the post
contract and editor schema; locate the BlogPost interface and add the
relatedProducts field using the imported Product type.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| import { ImageWidget } from "../admin/widgets.ts"; | ||
| import { PageInfo, Person, Thing } from "../commerce/types.ts"; | ||
| import { type Section } from "@deco/deco/blocks"; | ||
| import { Product } from "../commerce/types.ts"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether relatedProducts is already modeled/consumed elsewhere.
rg -n -C3 --type=ts '\brelatedProducts\b'
rg -n -C3 --type=ts '\binterface\s+BlogPost\b'
rg -n -C3 --type=ts '`@options` .*Product|ProductList|Catalog'Repository: deco-cx/apps
Length of output: 50370
🏁 Script executed:
cat -n blog/types.tsRepository: deco-cx/apps
Length of output: 7062
Add the missing relatedProducts field to complete the post contract.
Line 4 imports Product, but the BlogPost interface only includes relatedPosts and lacks a relatedProducts field. This type gap can break editor schema integration for related products functionality.
Proposed change
export interface BlogPost {
relatedPosts?: BlogPost[];
+ /**
+ * `@title` Produtos Relacionados
+ * `@description` Selecione produtos para exibir no post
+ * `@format` dynamic-options
+ * `@options` <use the same product loader path used by this PR>
+ */
+ relatedProducts?: Product[];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@blog/types.ts` at line 4, The BlogPost type is missing the relatedProducts
field though Product is imported; update the BlogPost interface to include a
relatedProducts property (e.g., relatedProducts?: Product[] or relatedProducts:
Product[] as appropriate for optionality) alongside the existing relatedPosts to
satisfy the post contract and editor schema; locate the BlogPost interface and
add the relatedProducts field using the imported Product type.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
blog/types.ts (1)
1-4:⚠️ Potential issue | 🟠 MajorPR description says “relatedProducts” but
BlogPostonly supportsrelatedPosts.
blog/types.ts’sexport interface BlogPostdefinesrelatedPosts?: BlogPost[]and has norelatedProductsfield (and noProductimport). There are norelatedProductsoccurrences anywhere underblog/(the only matches are invtex/loaders/legacy/relatedProductsLoader.ts).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@blog/types.ts` around lines 1 - 4, The PR claims support for "relatedProducts" but the BlogPost interface only has relatedPosts; either add a relatedProducts?: Product[] field to export interface BlogPost and import Product from "../commerce/types.ts" (ensure the new field is used where needed), or update the PR and any callers to consistently use relatedPosts; locate the interface named BlogPost and the relatedPosts property to apply the chosen fix so the type and documentation match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@blog/types.ts`:
- Around line 1-4: The PR claims support for "relatedProducts" but the BlogPost
interface only has relatedPosts; either add a relatedProducts?: Product[] field
to export interface BlogPost and import Product from "../commerce/types.ts"
(ensure the new field is used where needed), or update the PR and any callers to
consistently use relatedPosts; locate the interface named BlogPost and the
relatedPosts property to apply the chosen fix so the type and documentation
match.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 31c49377-8e68-4c4c-9ee3-2d2c8820540d
📒 Files selected for processing (2)
blog/sections/Template.tsxblog/types.ts
yuriassuncx
left a comment
There was a problem hiding this comment.
Great contribution — this PR adds two well-scoped features to the blog app that fill real gaps in the current type model:
Category.parent?: Category— enables a proper category hierarchy (subcategories) where today theCategoryinterface only supports flat structures (name+slug). This unlocks breadcrumbs, nested navigation, and filtered listing by parent. There is no native way to achieve this today.BlogPost.relatedPosts?: BlogPost[]— enables editorial curation of related posts directly from the CMS admin, using adynamic-optionspicker backed by the existingBlogPostList.tsloader. While it's possible to render a "related posts" section algorithmically (by filtering on the same category/tag via a separate loader), this approach gives editors fine-grained, per-post control — a different and complementary value.
Both fields use the @format dynamic-options annotation correctly, keeping the admin form clean with a dropdown instead of an unbounded nested editor.
This is approved in principle, but there are a couple of things to address before merging.
Required changes
1. Annotations must be in English
All JSDoc annotations in this codebase follow English conventions. Please translate the Portuguese strings in blog/types.ts:
// Category.parent — current (Portuguese)
/**
* @title Categoria Pai
* @description Selecione se esta categoria for uma subcategoria de outra
* @format dynamic-options
* @options apps/blog/loaders/CategoryList.ts
*/
// Category.parent — expected (English)
/**
* @title Parent Category
* @description Select if this category is a subcategory of another
* @format dynamic-options
* @options apps/blog/loaders/CategoryList.ts
*/// BlogPost.relatedPosts — current (Portuguese)
/**
* @title Posts Relacionados
* @description Busque posts já cadastrados pelo título
* @format dynamic-options
* @options apps/blog/loaders/BlogPostList.ts
*/
// BlogPost.relatedPosts — expected (English)
/**
* @title Related Posts
* @description Search for existing posts by title
* @format dynamic-options
* @options apps/blog/loaders/BlogPostList.ts
*/2. PR title vs. actual scope — please clarify
The PR title is feat: create related posts and products, and the Cubic summary also mentions relatedProducts. However, the final state of blog/types.ts only includes relatedPosts — there is no relatedProducts?: Product[] field in BlogPost.
Please do one of the following:
- Option A: Rename the PR to
feat: add related posts and parent categoryto match what is actually implemented. - Option B: Add
relatedProducts?: Product[]toBlogPostwith proper JSDoc (if the intent was always to include product linking), and make sure theProductimport is present and used.
This was also flagged by @coderabbitai in its last review pass — worth addressing.
Notes on CodeRabbit suggestions
CodeRabbit flagged an XSS concern (dangerouslySetInnerHTML without sanitization in Template.tsx, commit db0ea3f) suggesting the use of the existing sanitizeHtml utility from spire/utils/sanitizeHtml.ts. That finding is no longer applicable since commit d9f0695 reverted the Template.tsx content changes — the final diff leaves that file essentially untouched. No action needed there.
The relatedProducts mismatch flagged in the second and third CodeRabbit passes is the one that still stands and is covered above.
Once the annotation language is updated and the title/scope mismatch is resolved, this is ready to go. 🙌
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
Demonstration Link
Summary by cubic
Add related posts and products to blog posts and add categories with an optional parent to improve navigation and merchandising. Reverted earlier post template styling changes; no visual template changes in this PR.
New Features
relatedPostsviaapps/blog/loaders/BlogPostList.tsand relatedProducts from the catalog; dynamic options for selecting posts.Category.parentwith dynamic options viaapps/blog/loaders/CategoryList.ts.Refactors
Written for commit d9f0695. Summary will update on new commits. Review in cubic
Summary by CodeRabbit