blog: replace ad-hoc segment maps with a closed 5-category vocabulary - #1652
popovayoana wants to merge 3 commits into
Conversation
portaljs-is-now-ai-native, the Kyle rebuild post, and the Giftless/R2 post went live without a hero image, and the Kyle post also shipped without frontmatter tags. Adds Yoana's hero images following the existing /static/img/blog/<slug>/hero.png convention, and backfills tags on the Kyle post.
Labels on /blog were driven by two hand-maintained maps (CATEGORY_MAP, LABEL_OVERRIDES) keyed by post slug. Every new post had to be added to both or it silently showed no label chips, even with tags in its frontmatter β this happened to 4 posts in a row. Replaces both maps with a single closed vocabulary of 5 categories (PortalJS, PortalJS Cloud, CKAN, AI Integration, Open Data), driven by a `categories:` frontmatter field. A POST_CATEGORIES fallback map covers existing posts that predate this so nothing regresses. Filter buttons and label chips now read from the same list, so they can't drift apart, and getStaticProps throws if a post declares a category outside the five β a typo now fails the build instead of quietly rendering nothing. Documents the new frontmatter contract in CLAUDE.md so publishers (human or AI) see it before shipping a post.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
7 Skipped Deployments
|
π WalkthroughWalkthroughThe blog page replaces segment-based filtering with validated categories, fallback category labels, category icons, and category-aware cards. Three posts gain hero image metadata, one gains tags, and site guidance documents category and featured-post conventions. ChangesBlog category and metadata flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Visitor
participant BlogPage
participant BlogCards
Visitor->>BlogPage: select activeCategory
BlogPage->>BlogPage: filter posts with getPostCategories
BlogPage->>BlogCards: render category labels and filtered cards
Possibly related PRs
Suggested reviewers: π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ Generate docstrings
π§ͺ 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
π€ 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
`@site/content/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code.md`:
- Around line 6-11: Replace the frontmatter tags list with a categories list in
the blog post, mapping the existing labels to the allowed vocabulary only:
PortalJS, PortalJS Cloud, CKAN, AI Integration, or Open Data. Preserve the
existing image metadata and remove the obsolete tags property.
In `@site/pages/blog.tsx`:
- Around line 294-304: Update the category validation loop around
blog.categories to reject any defined categories value that is not an array,
adding an else-if branch that throws a clear error identifying the blog URL and
expected YAML list format. Preserve the existing invalid-category checks for
array values.
πͺ 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: a88c7726-bbb8-4061-8781-86f15909d942
β Files ignored due to path filters (3)
site/public/static/img/blog/portaljs-is-now-ai-native/hero.pngis excluded by!**/*.pngsite/public/static/img/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code/hero.pngis excluded by!**/*.pngsite/public/static/img/blog/scaling-portaljs-data-with-giftless-and-r2/hero.pngis excluded by!**/*.png
π Files selected for processing (5)
CLAUDE.mdsite/content/blog/portaljs-is-now-ai-native.mdsite/content/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code.mdsite/content/blog/scaling-portaljs-data-with-giftless-and-r2.mdsite/pages/blog.tsx
| tags: | ||
| - PortalJS | ||
| - AI | ||
| - civic data | ||
| - open data | ||
| image: "/static/img/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code/hero.png" |
There was a problem hiding this comment.
π― Functional Correctness | π Major | β‘ Quick win
Replace tags with categories using the allowed vocabulary.
As per coding guidelines, every blog post frontmatter must declare categories using only PortalJS, PortalJS Cloud, CKAN, AI Integration, or Open Data.
Additionally, the tags property is no longer rendered on the /blog page. The updated components (BlogCard, FeaturedBlogPost) receive labels={getPostCategories(blog)}, which evaluates to an array (even if empty) and overrides blog.tags entirely. To ensure these labels appear, they must be converted to categories.
π οΈ Proposed fix
-tags:
- - PortalJS
- - AI
- - civic data
- - open data
+categories:
+ - PortalJS
+ - AI Integration
+ - Open Data
image: "/static/img/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code/hero.png"π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| tags: | |
| - PortalJS | |
| - AI | |
| - civic data | |
| - open data | |
| image: "/static/img/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code/hero.png" | |
| categories: | |
| - PortalJS | |
| - AI Integration | |
| - Open Data | |
| image: "/static/img/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code/hero.png" |
π€ 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
`@site/content/blog/rebuilt-city-of-kyle-open-data-portal-in-30-minutes-with-claude-code.md`
around lines 6 - 11, Replace the frontmatter tags list with a categories list in
the blog post, mapping the existing labels to the allowed vocabulary only:
PortalJS, PortalJS Cloud, CKAN, AI Integration, or Open Data. Preserve the
existing image metadata and remove the obsolete tags property.
Source: Coding guidelines
| for (const blog of blogList) { | ||
| if (Array.isArray(blog?.categories)) { | ||
| const invalid = blog.categories.filter((c: string) => !CATEGORY_SET.has(c)) | ||
| if (invalid.length) { | ||
| throw new Error( | ||
| `Blog post "${blog.urlPath}" has invalid categories: ${invalid.join(', ')}. ` + | ||
| `Allowed categories are: ${CATEGORIES.join(', ')}.` | ||
| ) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
π― Functional Correctness | π‘ Minor | β‘ Quick win
Validate that blog.categories is an array.
If a post author incorrectly formats the frontmatter (e.g., using a plain string categories: PortalJS instead of a YAML list), Array.isArray(blog?.categories) evaluates to false, bypassing this validation. The build will then crash during the React render phase with a cryptic TypeError: declared.filter is not a function in getPostCategories.
Adding an else if branch prevents this by failing the build with a clear error message.
π οΈ Proposed fix
for (const blog of blogList) {
if (Array.isArray(blog?.categories)) {
const invalid = blog.categories.filter((c: string) => !CATEGORY_SET.has(c))
if (invalid.length) {
throw new Error(
`Blog post "${blog.urlPath}" has invalid categories: ${invalid.join(', ')}. ` +
`Allowed categories are: ${CATEGORIES.join(', ')}.`
)
}
+ } else if (blog?.categories !== undefined) {
+ throw new Error(
+ `Blog post "${blog.urlPath}" has an invalid categories format. Expected an array, got ${typeof blog.categories}.`
+ )
}
}π Committable suggestion
βΌοΈ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (const blog of blogList) { | |
| if (Array.isArray(blog?.categories)) { | |
| const invalid = blog.categories.filter((c: string) => !CATEGORY_SET.has(c)) | |
| if (invalid.length) { | |
| throw new Error( | |
| `Blog post "${blog.urlPath}" has invalid categories: ${invalid.join(', ')}. ` + | |
| `Allowed categories are: ${CATEGORIES.join(', ')}.` | |
| ) | |
| } | |
| } | |
| } | |
| for (const blog of blogList) { | |
| if (Array.isArray(blog?.categories)) { | |
| const invalid = blog.categories.filter((c: string) => !CATEGORY_SET.has(c)) | |
| if (invalid.length) { | |
| throw new Error( | |
| `Blog post "${blog.urlPath}" has invalid categories: ${invalid.join(', ')}. ` + | |
| `Allowed categories are: ${CATEGORIES.join(', ')}.` | |
| ) | |
| } | |
| } else if (blog?.categories !== undefined) { | |
| throw new Error( | |
| `Blog post "${blog.urlPath}" has an invalid categories format. Expected an array, got ${typeof blog.categories}.` | |
| ) | |
| } | |
| } |
π€ 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 `@site/pages/blog.tsx` around lines 294 - 304, Update the category validation
loop around blog.categories to reject any defined categories value that is not
an array, adding an else-if branch that throws a clear error identifying the
blog URL and expected YAML list format. Preserve the existing invalid-category
checks for array values.
Root CLAUDE.md is the PortalJS framework guide for external developers building portals with the template β it has nothing to do with publishing on portaljs.com's own marketing blog. Moves the category vocabulary rules into a new site/CLAUDE.md, scoped to this repo's internal site/ directory, so it's picked up when working on the site without polluting the framework-facing guide.
There was a problem hiding this comment.
Actionable comments posted: 2
π€ 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 `@site/CLAUDE.md`:
- Line 10: Update the category requirement in CLAUDE.md to apply only to new or
updated posts, matching the fallback behavior in blog.tsx for older posts
without frontmatter categories; alternatively, remove that fallback before
requiring categories on every post.
- Around line 12-14: Add the text language identifier to the fenced Markdown
block containing the comma-separated category list in CLAUDE.md, changing the
opening fence to specify text while preserving the list content.
πͺ 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: a6387678-e292-4779-9cc8-24d1937a1223
π Files selected for processing (1)
site/CLAUDE.md
|
|
||
| ## Blog posts (site/content/blog) | ||
|
|
||
| Every post's frontmatter must declare `categories:`, using ONLY these five values: |
There was a problem hiding this comment.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
Clarify the category requirement to match the fallback behavior.
site/pages/blog.tsx falls back to POST_CATEGORIES for older posts without frontmatter categories, so βEvery postβ overstates the current contract. Specify that new or updated posts must declare categories, or remove the fallback before enforcing this requirement.
Also applies to: 22-25
π€ 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 `@site/CLAUDE.md` at line 10, Update the category requirement in CLAUDE.md to
apply only to new or updated posts, matching the fallback behavior in blog.tsx
for older posts without frontmatter categories; alternatively, remove that
fallback before requiring categories on every post.
| ``` | ||
| PortalJS, PortalJS Cloud, CKAN, AI Integration, Open Data | ||
| ``` |
There was a problem hiding this comment.
π Maintainability & Code Quality | π‘ Minor | β‘ Quick win
Add a language identifier to the fenced block.
This triggers Markdownlint MD040. Use text for the comma-separated category list.
Proposed fix
-```
+```text
PortalJS, PortalJS Cloud, CKAN, AI Integration, Open Data
</details>
<!-- suggestion_start -->
<details>
<summary>π Committable suggestion</summary>
> βΌοΈ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
π§° Tools
πͺ markdownlint-cli2 (0.23.0)
[warning] 12-12: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
π€ 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 `@site/CLAUDE.md` around lines 12 - 14, Add the text language identifier to the
fenced Markdown block containing the comma-separated category list in CLAUDE.md,
changing the opening fence to specify text while preserving the list content.
Source: Linters/SAST tools
Stacked on #1651
This PR is stacked on #1651 (content fixes) β the diff below includes that PR's commit until it merges. Only the commits after that are new.
Summary
/blogwere driven by two hand-maintained maps (CATEGORY_MAP,LABEL_OVERRIDES) keyed by post slug, separate from each post's own frontmattertags:. Every new post had to be manually added to both maps or it silently showed no label chips at all β this happened to 4 posts in a row this week.PortalJS,PortalJS Cloud,CKAN,AI Integration,Open Data.categories:in frontmatter as the source of truth. APOST_CATEGORIESfallback map covers existing posts that predate this so nothing regresses.getStaticPropsthrows a build error if a post declares a category outside the five β a typo now fails the build loudly instead of quietly rendering no labels.site/CLAUDE.md(not the repo rootCLAUDE.md, which is the PortalJS framework guide for external developers building portals β unrelated to publishing on our own marketing site) so publishers, human or AI, see the rule before shipping a post.Test plan
npm run devlocally, confirmed/blogrenders with exactly the 5 filter buttons (All / PortalJS / PortalJS Cloud / CKAN / AI Integration / Open Data)POST_CATEGORIESfallback (no frontmatter changes needed)main, no conflictsπ€ Generated with Claude Code