Skip to content

Comments

refactor(articles): update article slugs to be relative and adjust mapping logic#704

Merged
michnowak merged 7 commits intomainfrom
fix/richText-component
Feb 20, 2026
Merged

refactor(articles): update article slugs to be relative and adjust mapping logic#704
michnowak merged 7 commits intomainfrom
fix/richText-component

Conversation

@michnowak
Copy link
Contributor

@michnowak michnowak commented Feb 18, 2026

What does this PR do?

  • My bugfix

Related Ticket(s)

  • Notion Ticket

Key Changes

  • How does the code change address the issue? Describe, at a high level, what was done to affect change.
  • What side effects does this change have? This is the most important question to answer, as it can point out problems where you are making too many changes in one commit or branch. One or two bullet points for related changes may be okay, but five or six are likely indicators of a commit that is doing too many things.

How to test

  • Create a detailed description of what you need to do to set this PR up. ie: Does it need migrations? Do you need to install something?
  • Create a step by step list of what the engineer needs to do to test.

Media (Loom or gif)

  • Insert media here (if applicable)

Summary by CodeRabbit

  • New Features

    • Zendesk article links converted to relative slugs for cleaner content linking
    • Next.js now allows loading images from Zendesk domains
    • Improved image rendering in rich text content
  • Bug Fixes

    • More flexible slug matching to improve article lookup and routing
  • Chores

    • Pinned markdown rendering dependency to 9.7.0
    • Mocked article and category slugs normalized to relative paths
  • Style

    • Removed two image-related typography variants

@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
o2s-docs Skipped Skipped Feb 19, 2026 3:17pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 18, 2026

Walkthrough

Normalized article/category slugs (removed leading slashes), changed slug matching to suffix/endsWith across integrations, made mocked service emissions lazy, added Zendesk article-link → slug replacement in the Zendesk mapper, adjusted RichText image rendering, and pinned markdown-to-jsx to 9.7.0.

Changes

Cohort / File(s) Summary
Dependency pins
apps/frontend/package.json, packages/ui/package.json
Pinned markdown-to-jsx from ^9.7.49.7.0.
Mock data: slugs made relative
packages/integrations/mocked/src/modules/articles/mocks/article*.content.mocks.ts, packages/integrations/mocked/src/modules/articles/mocks/categories.mocks.ts
Removed leading / from article and category slug values across EN/DE/PL mocks.
Mocked articles: mapper & service
packages/integrations/mocked/src/modules/articles/articles.mapper.ts, packages/integrations/mocked/src/modules/articles/articles.service.ts
mapArticle now uses suffix matching (endsWith) for slug lookup; service returns defer(() => of(...)) for lazy observable emission.
Strapi integration: slug matching & mapping
packages/integrations/strapi-cms/src/modules/articles/articles.mapper.ts, packages/integrations/strapi-cms/src/modules/articles/articles.service.ts, .../graphql/queries/getArticle.graphql
Category slug no longer composes parent path; GraphQL query uses endsWith and service normalizes incoming slugs (strip leading /, compare suffix).
Zendesk mapper: link replacement
packages/integrations/zendesk/src/modules/articles/zendesk-article.mapper.ts
Added replaceZendeskArticleLinks to convert Zendesk article URLs in HTML bodies to relative slugs; applied during section parsing.
UI: RichText image handling & typography
packages/ui/src/components/RichText/RichText.tsx, packages/ui/src/elements/typography.tsx
Introduced internal ImgComp using Image component; replaced Markdown img override to use ImgComp; removed image and imageCaption typography variants.
Next config: remote images
apps/frontend/next.config.ts
Added remote image pattern for *.zendesk.com over HTTPS.
Changeset
.changeset/poor-badgers-grow.md
Recorded package version bumps and noted slug/mapping normalization changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • marcinkrasowski

Poem

🐇 I nibbled slashes from every little track,
Turned links to slugs and never looked back,
Images snug in a component nest,
Observables wait till you ask for the rest,
A joyful hop — the mocks are now compact. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description uses the template structure but contains only placeholder text with no actual content filled in, providing no meaningful information about the changes, rationale, side effects, or testing instructions. Replace all placeholder text with substantive information: explain what the change addresses, describe side effects and potential issues, and provide detailed setup and testing steps.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: converting article slugs to relative paths and updating the associated mapping logic throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/richText-component

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.

@michnowak michnowak enabled auto-merge (squash) February 18, 2026 13:49
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: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/integrations/mocked/src/modules/articles/mocks/article11.content.mocks.ts (1)

5-26: ⚠️ Potential issue | 🟡 Minor

Pre-existing category.id inconsistency for art-011 across locales.

The EN version of art-011 (Line 24) uses category.id: 'warranty-and-repair' (hyphens), while the DE (Line 189) and PL (Line 256) counterparts use category.id: 'warranty_repair' (underscore). Since this PR touches this file, it's a good opportunity to align all three locales. If the system performs any category-based grouping or filtering on category.id, this mismatch would cause art-011 to appear under a different category in EN than in DE/PL.

🛠️ Suggested alignment
 // EN art-011 (line 24)
 category: {
-    id: 'warranty-and-repair',
+    id: 'warranty_repair',
     title: 'Warranty & Repair',
 },

Also applies to: 169-232, 235-259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/integrations/mocked/src/modules/articles/mocks/article11.content.mocks.ts`
around lines 5 - 26, The category.id for article 'art-011' is inconsistent
across locales (EN uses 'warranty-and-repair' while DE/PL use
'warranty_repair'); pick one canonical id and make them consistent by updating
the category.id values for article id 'art-011' in the EN, DE, and PL mock
entries (update the 'category' object inside the article entries), ensuring all
three use the same string (either 'warranty-and-repair' or 'warranty_repair') so
category-based grouping/filtering behaves consistently.
🧹 Nitpick comments (3)
packages/integrations/zendesk/src/modules/articles/zendesk-article.mapper.ts (1)

54-56: JSDoc example implies lowercasing that the function doesn't perform

The comment states Returns: "12345-article-title" (all lowercase), but extractSlugFromUrl returns the slug in whatever case the original URL uses (e.g. 12345-Article-Title). If Zendesk always uses lowercase slugs this is harmless in practice, but the comment is misleading.

📝 Proposed doc fix
- * Returns: "12345-article-title" or just the ID as fallback
+ * Returns: "12345-Article-Title" (preserves original URL casing) or just the ID as fallback
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/integrations/zendesk/src/modules/articles/zendesk-article.mapper.ts`
around lines 54 - 56, The JSDoc for extractSlugFromUrl incorrectly claims the
slug is lowercased; update the comment to reflect actual behavior by changing
the example/return line to indicate the function preserves original case (e.g.,
Returns: "12345-Article-Title" or just the ID as fallback) or explicitly state
"preserves original case" so the docs match the extractSlugFromUrl
implementation.
packages/ui/src/components/RichText/RichText.tsx (1)

51-60: alt={props.alt} is redundant — ...props already includes it.

alt is included in the ...props spread, so the trailing alt={props.alt} simply overrides with the same value. It's harmless but adds noise.

♻️ Proposed cleanup
  const ImgComp: FC<Readonly<React.ImgHTMLAttributes<HTMLImageElement> & { children?: ReactNode }>> = ({
      children: _children,
      ...props
  }) => {
      return (
          <Typography variant="image" asChild>
-             <img {...props} alt={props.alt} />
+             <img {...props} />
          </Typography>
      );
  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/RichText/RichText.tsx` around lines 51 - 60, The
ImgComp component spreads image attributes via ...props and then redundantly
sets alt={props.alt}; remove the redundant alt={props.alt} override in the
ImgComp return so the <img> relies solely on the spread props (locate the
ImgComp functional component to update the JSX).
packages/integrations/mocked/src/modules/articles/articles.service.ts (1)

13-13: defer wrapping is correct for getCategory — consider applying it to other sync-mapper methods too.

Without defer, a NotFoundException thrown by mapCategory would propagate synchronously before the Observable is created, potentially bypassing NestJS exception filters that handle errors from the Observable stream. Wrapping in defer ensures the error is emitted as an Observable error on subscription, which is the correct RxJS pattern.

getCategoryList and getArticle currently don't throw, but wrapping their sync mapper calls in defer as well would make the pattern uniform and safe against future changes that add throwing behavior.

♻️ Proposed optional consistency fix
  getCategory(options: Articles.Request.GetCategoryParams): Observable<Articles.Model.Category> {
      return defer(() => of(mapCategory(options.locale, options.id)));
  }

  getCategoryList(options: Articles.Request.GetCategoryListQuery): Observable<Articles.Model.Categories> {
-     return of(mapCategories(options.locale, options));
+     return defer(() => of(mapCategories(options.locale, options)));
  }

  getArticle(params: Articles.Request.GetArticleParams): Observable<Articles.Model.Article | undefined> {
-     return of(mapArticle(params.locale, params.slug));
+     return defer(() => of(mapArticle(params.locale, params.slug)));
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/integrations/mocked/src/modules/articles/articles.service.ts` at
line 13, The current getCategory uses defer(() => of(mapCategory(...))) to
ensure synchronous exceptions become Observable errors; apply the same pattern
to the other sync-mapper methods so thrown errors don't escape before
subscription: wrap mapArticleList call in getCategoryList and mapArticle call in
getArticle with defer(() => of(...)) (referencing getCategoryList, getArticle,
mapArticleList, mapArticle) so both methods emit errors through the Observable
stream consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/integrations/mocked/src/modules/articles/articles.mapper.ts`:
- Line 54: The current article lookup uses articles.find((article) =>
slug.endsWith(article.slug)) which can produce false positives across
path-segment boundaries; change the predicate in that articles.find call to
require either an exact match (slug === article.slug) or a path-segment anchored
match (slug.endsWith('/' + article.slug)) so only whole-segment matches (not
bare suffixes) are returned.

In
`@packages/integrations/zendesk/src/modules/articles/zendesk-article.mapper.ts`:
- Around line 95-98: The slug extraction currently allows fragments and query
strings because extractSlugFromUrl uses a too-broad character class (e.g.,
[^/]+) so strings like "Title#anchor" or "Title?query" are captured; update
extractSlugFromUrl to tighten the article-slug capture to exclude "?" and "#"
(use a character class like [^/?#]+) so it stops at query or fragment
separators, then keep replaceZendeskArticleLinks as-is to pass the cleaned slug
through; ensure you update any related regex groups in extractSlugFromUrl to
reflect the new class so match[2] contains only the valid slug.

In `@packages/ui/package.json`:
- Line 69: The package.json change pins "markdown-to-jsx" to 9.7.0 which removes
important bug fixes; revert the dependency in packages/ui's package.json to a
caret range that includes 9.7.4 (e.g., "^9.7.4") or the previous version
specifier, or alternatively add an explicit resolutions/overrides entry that
forces 9.7.4+ while documenting any known incompatibility; if you must pin to
9.7.0, add a clear comment and a linked issue/repro steps explaining the blocker
so reviewers can evaluate the regression.

---

Outside diff comments:
In
`@packages/integrations/mocked/src/modules/articles/mocks/article11.content.mocks.ts`:
- Around line 5-26: The category.id for article 'art-011' is inconsistent across
locales (EN uses 'warranty-and-repair' while DE/PL use 'warranty_repair'); pick
one canonical id and make them consistent by updating the category.id values for
article id 'art-011' in the EN, DE, and PL mock entries (update the 'category'
object inside the article entries), ensuring all three use the same string
(either 'warranty-and-repair' or 'warranty_repair') so category-based
grouping/filtering behaves consistently.

---

Duplicate comments:
In `@apps/frontend/package.json`:
- Line 63: The package.json change pins "markdown-to-jsx" to 9.7.0 which
duplicates the problematic downgrade in packages/ui; restore a semver range that
includes the fixed release (e.g., change the dependency for "markdown-to-jsx"
back to "^9.7.4" or match whatever range is used in packages/ui) so the fix for
verbatim rendering of <pre>/<script>/<style>/<textarea> is included; ensure the
version string is consistent with packages/ui to avoid conflicting dependency
resolutions.

---

Nitpick comments:
In `@packages/integrations/mocked/src/modules/articles/articles.service.ts`:
- Line 13: The current getCategory uses defer(() => of(mapCategory(...))) to
ensure synchronous exceptions become Observable errors; apply the same pattern
to the other sync-mapper methods so thrown errors don't escape before
subscription: wrap mapArticleList call in getCategoryList and mapArticle call in
getArticle with defer(() => of(...)) (referencing getCategoryList, getArticle,
mapArticleList, mapArticle) so both methods emit errors through the Observable
stream consistently.

In
`@packages/integrations/zendesk/src/modules/articles/zendesk-article.mapper.ts`:
- Around line 54-56: The JSDoc for extractSlugFromUrl incorrectly claims the
slug is lowercased; update the comment to reflect actual behavior by changing
the example/return line to indicate the function preserves original case (e.g.,
Returns: "12345-Article-Title" or just the ID as fallback) or explicitly state
"preserves original case" so the docs match the extractSlugFromUrl
implementation.

In `@packages/ui/src/components/RichText/RichText.tsx`:
- Around line 51-60: The ImgComp component spreads image attributes via ...props
and then redundantly sets alt={props.alt}; remove the redundant alt={props.alt}
override in the ImgComp return so the <img> relies solely on the spread props
(locate the ImgComp functional component to update the JSX).

@github-actions
Copy link
Contributor

github-actions bot commented Feb 18, 2026

Coverage Report for packages/configs/vitest-config

Status Category Percentage Covered / Total
🔵 Lines 80.87% 1192 / 1474
🔵 Statements 80.34% 1267 / 1577
🔵 Functions 78.66% 446 / 567
🔵 Branches 72.13% 1017 / 1410
File CoverageNo changed files found.
Generated in workflow #333 for commit 4621763 by the Vitest Coverage Report Action

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/poor-badgers-grow.md:
- Around line 2-5: Update the changeset to use patch bumps for the bugfix:
replace 'minor' with 'patch' for the packages '@o2s/integrations.zendesk',
'@o2s/integrations.mocked', '@o2s/frontend', and '@o2s/ui' in the changeset
entry so the versioning reflects a bugfix rather than a minor feature addition;
if the change truly introduces a new public behavior, instead leave as minor and
add an explicit note explaining the breaking/behavioral change for consumers.

@vercel vercel bot temporarily deployed to Preview – o2s-docs February 19, 2026 15:17 Inactive
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: 6

Caution

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

⚠️ Outside diff range comments (2)
packages/integrations/strapi-cms/src/modules/articles/articles.service.ts (2)

63-78: ⚠️ Potential issue | 🟠 Major

endsWith matching is not path-segment-safe and the fallback silently serves the wrong article

Two distinct bugs in the resolution logic at line 73:

Bug 1 — Path-segment-unsafe endsWith:
String.prototype.endsWith operates at character level, not path-segment level. Suppose the DB contains two articles with slugs "my-article" and "sub/my-article". A request for "sub/my-article" runs:

"sub/my-article".endsWith("my-article")     → true  ← wrong match if first in array
"sub/my-article".endsWith("sub/my-article") → true  ← correct match

Array.find returns the first truthy match. Because the GraphQL endsWith: $slug filter sends only slugSuffix (the last segment), both articles can appear in the result, and whichever is first determines the winner. A request for /support/my-article could silently return the root-level my-article article instead.

The path-boundary–safe predicate is:

slug === a.slug || slug.endsWith('/' + a.slug)

To prefer the most specific match when several candidates are valid, sort by slug length descending before calling find.

Bug 2 — Silent wrong-article fallback:
|| pages.data.articles[0]! returns the first article when no slug match is found, silently serving wrong content. Should throw NotFoundException.

🐛 Proposed fix
-               const article = pages.data.articles.find((a) => slug.endsWith(a.slug)) || pages.data.articles[0]!;
+               const article = pages.data.articles
+                   .filter((a) => slug === a.slug || slug.endsWith('/' + a.slug))
+                   .sort((a, b) => b.slug.length - a.slug.length)[0];
+
+               if (!article) {
+                   throw new NotFoundException();
+               }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/integrations/strapi-cms/src/modules/articles/articles.service.ts`
around lines 63 - 78, The getArticle method currently uses slug.endsWith(a.slug)
and falls back to pages.data.articles[0], which can return the wrong article;
update the resolution logic so you first sort pages.data.articles by slug length
descending, then find an article using a path-segment-safe predicate (slug ===
a.slug || slug.endsWith('/' + a.slug)), and if no match is found throw
NotFoundException instead of returning pages.data.articles[0]; keep the rest of
the flow (mapArticle, baseUrl, graphqlService.getArticle, forkJoin) unchanged.

63-78: ⚠️ Potential issue | 🟠 Major

endsWith matching is not path-segment-safe, and the fallback silently serves the wrong article

Two distinct bugs on line 73:

Bug 1 — Array ordering determines correctness.
slugSuffix (the last path segment) is sent to GraphQL's endsWith filter, which returns every DB article whose slug ends with that segment — including slugs like "my-article" and "sub/my-article". The TypeScript find then checks slug.endsWith(a.slug):

slug = "sub/my-article"
"sub/my-article".endsWith("my-article")     → true  ← wrong match if first in the array
"sub/my-article".endsWith("sub/my-article") → true  ← correct, but only checked if "my-article" didn't arrive first

Because Array.find returns the first truthy result, whichever candidate the DB returns first wins, making routing non-deterministic when multiple articles share a slug suffix.

Bug 2 — Silent wrong-article fallback.
|| pages.data.articles[0]! silently serves unrelated content when no article matches. Should throw NotFoundException.

The path-segment-safe fix is to anchor the match to a / boundary and sort candidates by specificity so the most specific slug always wins:

🐛 Proposed fix
-               const article = pages.data.articles.find((a) => slug.endsWith(a.slug)) || pages.data.articles[0]!;
+               const article = pages.data.articles
+                   .filter((a) => slug === a.slug || slug.endsWith('/' + a.slug))
+                   .sort((a, b) => b.slug.length - a.slug.length)[0];
+
+               if (!article) {
+                   throw new NotFoundException();
+               }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/integrations/strapi-cms/src/modules/articles/articles.service.ts`
around lines 63 - 78, The current getArticle implementation uses endsWith
unsafely and falls back to pages.data.articles[0], causing wrong/undeterministic
matches; update getArticle so after receiving pages.data.articles you (1)
perform a path-segment-safe match: consider an article a a candidate if either
a.slug === slugSuffix or slug === a.slug or slug.endsWith('/' + a.slug) (this
anchors the suffix to a segment boundary), (2) sort candidates by specificity
(e.g., descending a.slug.length) and pick the first result so the most specific
slug wins, and (3) if no candidate remains, throw NotFoundException instead of
returning pages.data.articles[0]; adjust references inside getArticle (slug,
slugSuffix, pages.data.articles, mapArticle) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/frontend/next.config.ts`:
- Around line 41-44: The images config entry using protocol: 'https' and
hostname: '*.zendesk.com' is too broad and missing a pathname restriction;
either replace '*.zendesk.com' with the exact Zendesk subdomain(s) your app uses
(pin the known hostnames) or, if you truly cannot enumerate them, add a
restrictive pathname field to this same config object to limit allowed URL paths
and avoid implicit '**' wildcard behavior; also verify whether you need a
single-level '*' versus multi-level '**' for the hostname and adjust accordingly
to match actual Zendesk image hostnames.

In `@packages/integrations/strapi-cms/src/modules/articles/articles.service.ts`:
- Around line 64-65: The slug handling allows empty or "/" to produce slugSuffix
= "" which matches every article; add an early guard before computing
slug/slugSuffix: check params.slug (trimmed) and if it's empty or exactly "/"
then return a safe missing result (e.g., null or a 404) instead of querying;
keep references to params.slug, slug, and slugSuffix in your change and ensure
downstream GraphQL filter logic never receives an empty string.
- Around line 64-65: The code computes slugSuffix from params.slug but doesn't
guard against root/empty slugs so slugSuffix can be "" which matches everything;
update the logic in the articles service (where params.slug, slug, slugSuffix
and the subsequent endsWith checks are used) to first normalize params.slug
(remove leading slash) and then explicitly handle an empty string by returning
null or throwing a not-found error (or skipping the Strapi query) instead of
passing "" into the query; ensure any use of slugSuffix in the Strapi query or
TypeScript endsWith() check is skipped or short-circuited when slug === "" to
prevent matching all articles.

In
`@packages/integrations/strapi-cms/src/modules/articles/graphql/queries/getArticle.graphql`:
- Around line 1-5: The GraphQL query getArticle currently uses the endsWith
filter on articles (filters: { slug: { endsWith: $slug } }) which causes suffix
matches; change the filter to use eq with the full normalized slug (filters: {
slug: { eq: $slug } }) and update the caller in the service that invokes
getArticle to pass the complete normalized slug (not just the slugSuffix).
Ensure the service replaces any existing logic that built or passed slugSuffix
and stops relying on the multi-candidate fallback (and remove/adjust the
client-side fallback comparison like slug.endsWith(a.slug)) so the query returns
a single exact match predictably.
- Around line 1-5: The GraphQL query getArticle currently filters articles with
filters: { slug: { endsWith: $slug } } which causes broad matches; change the
filter to use exact equality (slug: { eq: $slug }) and update the service call
that supplies the variable (stop passing only the last segment, e.g., slugSuffix
or similar) to pass the full normalized slug path instead (the variable name
should be slug) so the query returns a single unambiguous article without
post-filtering via .find().

In `@packages/ui/src/components/RichText/RichText.tsx`:
- Around line 53-70: The ImgComp component currently uses Next.js Image with the
fill prop and forceful CSS overrides which break the fill contract and also
renders when src is empty; change ImgComp so it returns null when src is falsy,
remove the fill prop and the forced/important positioning classes (e.g., remove
className="relative! h-auto!" on the Image and stop coercing src with (src as
string) || ''), and instead render Image with explicit sizing (width/height or
rely on sizes with a natural layout) and keep the wrapper as the layout
container; update references in ImgComp to use the actual src/alt only when
present and ensure object-contain is applied to an absolutely positioned image
only if using fill, otherwise use sizing to preserve aspect ratio.

---

Outside diff comments:
In `@packages/integrations/strapi-cms/src/modules/articles/articles.service.ts`:
- Around line 63-78: The getArticle method currently uses slug.endsWith(a.slug)
and falls back to pages.data.articles[0], which can return the wrong article;
update the resolution logic so you first sort pages.data.articles by slug length
descending, then find an article using a path-segment-safe predicate (slug ===
a.slug || slug.endsWith('/' + a.slug)), and if no match is found throw
NotFoundException instead of returning pages.data.articles[0]; keep the rest of
the flow (mapArticle, baseUrl, graphqlService.getArticle, forkJoin) unchanged.
- Around line 63-78: The current getArticle implementation uses endsWith
unsafely and falls back to pages.data.articles[0], causing wrong/undeterministic
matches; update getArticle so after receiving pages.data.articles you (1)
perform a path-segment-safe match: consider an article a a candidate if either
a.slug === slugSuffix or slug === a.slug or slug.endsWith('/' + a.slug) (this
anchors the suffix to a segment boundary), (2) sort candidates by specificity
(e.g., descending a.slug.length) and pick the first result so the most specific
slug wins, and (3) if no candidate remains, throw NotFoundException instead of
returning pages.data.articles[0]; adjust references inside getArticle (slug,
slugSuffix, pages.data.articles, mapArticle) accordingly.

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.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In @.changeset/poor-badgers-grow.md:
- Around line 2-5: The changeset currently marks '@o2s/integrations.zendesk' and
'@o2s/ui' as minor while the reviewer notes those changes are consumer-visible;
decide whether those changes are truly internal: if the zendesk change to
"replacing article body links with relative slugs" and the ui changes (new
ImgComp, Image rendering behavior, pinning markdown-to-jsx to 9.7.0) are
intended to be public API changes keep them as minor, otherwise change their
entries in the .changeset (update the lines for '@o2s/integrations.zendesk' and
'@o2s/ui') from minor to patch to reflect purely internal/bugfix updates so the
release type matches the actual consumer-visible impact.

@michnowak michnowak merged commit 1804016 into main Feb 20, 2026
13 checks passed
@michnowak michnowak deleted the fix/richText-component branch February 20, 2026 07:32
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.

2 participants