feat(web): add responsive kanban roadmap with GitHub issues#1971
feat(web): add responsive kanban roadmap with GitHub issues#1971ComputelessComputer merged 9 commits intomainfrom
Conversation
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
✅ Deploy Preview for hyprnote ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for hyprnote-storybook ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the 📝 WalkthroughWalkthroughReplaces a hard-coded roadmap UI with a content-driven implementation: adds a Changes
Sequence Diagram(s)sequenceDiagram
participant Browser
participant Route as Roadmap Route
participant Content as allRoadmaps
participant MDX as MDX renderer
participant GitHub as GitHub iframe
Browser->>Route: GET /_view/roadmap or /_view/roadmap/:slug
Route->>Content: read allRoadmaps / find by slug (getRoadmapItems)
Content-->>Route: roadmap items (meta, mdx string, githubIssues)
Route-->>Browser: render page (Kanban/Columns or item page) with MDX payload
Browser->>MDX: hydrate/getMDXComponent to render MDX per card
MDX-->>Browser: rendered components (mermaid, embeds)
alt item has GitHub issues
Browser->>GitHub: load issue preview iframe(s)
GitHub-->>Browser: issue preview content
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_view/roadmap.tsx(5 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/routes/_view/roadmap.tsx
🪛 GitHub Actions: .github/workflows/web_ci.yaml
apps/web/src/routes/_view/roadmap.tsx
[error] 343-343: src/routes/_view/roadmap.tsx(343,3): error TS6133: 'status' is declared but its value is never read.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: fmt
🔇 Additional comments (5)
apps/web/src/routes/_view/roadmap.tsx (5)
24-51: LGTM!The type definitions are well-structured and clearly model the GitHub API response and the transformed roadmap data.
55-87: LGTM!The categorization logic correctly prioritizes closed state, then checks for in-progress labels, with a sensible fallback to "planned". The description truncation is handled cleanly.
118-204: LGTM!The component correctly handles loading, error, and success states. The filtering logic is straightforward and the conditional rendering is clean.
206-300: LGTM!The Kanban view components are well-structured with proper responsive hiding, show-more functionality, and status-based border styling.
407-445: LGTM!The card content handles compact/full modes elegantly with appropriate text truncation and label overflow indicators.
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
|
The latest updates on your projects. Learn more about Argos notifications ↗︎ Awaiting the start of a new Argos build… |
Replace the previous "planned" status and columns with a "todo" column to match requested columns: todo, in progress, done. Update the RoadmapStatus type and status mapping, rename and filter planned -> todo, and pass todo into KanbanView and ColumnView. Swap column titles/icons so columns render as To Do, In Progress, and Done, and update status-based styling to use "todo" where appropriate.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
apps/web/content-collections.ts (1)
404-446: Roadmap collection schema and transform look consistent; consider minor robustness tweaks.The roadmap collection mirrors existing patterns (schema + MDX compile + slug) and aligns with the MDX frontmatter in this PR. The GitHub issue extraction regex is safe but quite strict (no query strings or fragments, duplicates allowed). If you expect variations like
?tab=commentsor repeated links, you might want to:
- Loosen the regex to allow optional
[\?#][^\s]*tail.- Optionally
Array.from(new Set(...))to dedupegithubIssues.Not required for correctness, but would make the extractor more robust.
Also applies to: 448-459
apps/web/content/roadmap/mobile-app.mdx (1)
1-14: Content matches roadmap schema; link formatting tweak is optional.Frontmatter aligns with the
roadmapcollection schema (status: "todo", labels array, dates as strings), so this item will hydrate cleanly intoRoadmapItem. If you want slightly better readability and accessibility, you could convert the inline issue URLs in the task list to Markdown links ([iOS app development](https://github.com/...)), but it's not required for functionality.apps/web/content/roadmap/pdf-export.mdx (1)
1-15: Schema alignment looks good; Markdown issue links could be friendlier.This entry’s frontmatter (status, dates, labels) is consistent with the roadmap collection and
RoadmapStatus/RoadmapItemusage, so it should render correctly in the Kanban. As a small UX polish, you could wrap the issue URLs in Markdown links ([Basic PDF generation](...)) to improve readability, but it’s optional.apps/web/src/routes/_view/roadmap.tsx (3)
23-51: RoadmapItem model is consistent with content; consider explicit ordering if item count grows.
RoadmapStatusmatches the Zod enum in theroadmapcollection, andRoadmapItemmirrors the fields returned fromallRoadmaps. Thelabels || []andgithubIssues || []defaults prevent undefined access later, which is good.Right now
getRoadmapItems()preserves the underlying collection order. If you expect more items over time, you might want to sort (e.g., bycreatedorupdated) before splitting intodone/in-progress/todoso column ordering is predictable across builds.Also applies to: 54-58
67-80: Desktop Kanban layout behaves correctly; show‑more logic is sound.The split into
KanbanView(desktop) andColumnView(mobile) viahidden lg:grid/lg:hiddenavoids double-rendering on a single viewport. InKanbanColumn,DEFAULT_VISIBLE_ITEMSand thehasMore/showAlllogic are correct and handle empty columns gracefully with the “No items” placeholder.If you later add significantly more roadmap items, you may want to consider per-status sorting (e.g., newest first) inside each column, but the current implementation is functionally solid.
Also applies to: 107-201
203-284: Mobile ColumnView matches desktop semantics and hides empty sections.
ColumnViewreuses the samedone/inProgress/todoarrays andColumnSectioncleanly early-returns whenitems.length === 0, so you don’t show empty headings on small screens. The mobile-specific cap of 3 items with a “Show more items” CTA mirrors the desktop behavior well.No functional issues here; just note that
"Show X more items"will always use the plural, which is fine but could be refined later if you want singular/plural handling.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/web/content-collections.ts(2 hunks)apps/web/content/roadmap/mobile-app.mdx(1 hunks)apps/web/content/roadmap/pdf-export.mdx(1 hunks)apps/web/content/roadmap/real-time-collaboration.mdx(1 hunks)apps/web/src/routes/_view/roadmap.tsx(5 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/content/roadmap/real-time-collaboration.mdx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/routes/_view/roadmap.tsxapps/web/content-collections.ts
**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces
Files:
apps/web/content-collections.ts
🧬 Code graph analysis (1)
apps/web/src/routes/_view/roadmap.tsx (1)
packages/utils/src/cn.ts (1)
cn(20-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: ci (macos, macos-14)
- GitHub Check: fmt
🔇 Additional comments (1)
apps/web/src/routes/_view/roadmap.tsx (1)
286-350: MDX rendering in RoadmapCard is correct; memoization looks appropriate.Using
useMemo(() => getMDXComponent(item.mdx.code), [item.mdx.code])is a good way to avoid recompiling MDX on every render, and constraining the dependency toitem.mdx.codeavoids unnecessary recomputes. Label rendering with a cap (slice(0, compact ? 2 : 4)) plus a+Nbadge works well for dense cards.No issues from a React/TS standpoint; this component should be performant enough for the expected roadmap size.
| function GitHubIssuePreview({ url }: { url: string }) { | ||
| return ( | ||
| <div className="mt-3 pt-3 border-t border-neutral-100"> | ||
| <a | ||
| href={url} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className={cn([ | ||
| "inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs font-medium", | ||
| config.bg, | ||
| config.border, | ||
| config.text, | ||
| "flex items-center gap-2 text-sm text-stone-500", | ||
| "hover:text-stone-700 transition-colors", | ||
| ])} | ||
| > | ||
| <Icon icon={config.icon} /> | ||
| <span>{config.label}</span> | ||
| <Icon icon="mdi:github" className="text-lg" /> | ||
| <span>View on GitHub</span> | ||
| <Icon icon="mdi:open-in-new" className="text-xs" /> | ||
| </a> | ||
| <div className="mt-2 rounded-sm overflow-hidden border border-neutral-200"> | ||
| <iframe | ||
| src={url} | ||
| className="w-full h-[400px] bg-white" | ||
| title="GitHub Issue Preview" | ||
| sandbox="allow-same-origin allow-scripts" | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
GitHub X-Frame-Options CSP frame-ancestors headers third-party embedding policy
💡 Result:
Short answer: GitHub blocks third‑party embedding by default. It sends both X-Frame-Options and a CSP frame-ancestors policy (to cover older and newer browsers) that prevent framing of github.com pages, with only a few explicit exceptions (rendering endpoints, the Gist embed endpoint, and the GitHub Pages preview/related preview endpoints). Use the supported embed mechanisms (Gist embed script, GitHub Pages, or their allowed render endpoints) rather than trying to iframe github.com pages. [1][2][3]
Sources
- GitHub engineering: “GitHub’s CSP journey” — explains using both X-Frame-Options + CSP frame-ancestors, default deny framing, and the limited exceptions (render domain, Gist embed, Pages preview). [1]
- Gist embed documentation/examples — Gists are intended to be embedded via the provided embed script/endpoint. [2]
- CSP/frame-ancestors overview (behavior and why it replaces X-Frame-Options). [3]
🏁 Script executed:
# Check the codebase for GitHubIssuePreview usage patterns and context
rg -n "GitHubIssuePreview" --type ts --type tsx -B 3 -A 3Repository: fastrepl/hyprnote
Length of output: 88
🏁 Script executed:
# Search for any GitHub API integration or preview implementations
rg -n "github.*api\|api.*github" --type ts --type tsx -i -B 2 -A 2Repository: fastrepl/hyprnote
Length of output: 88
🏁 Script executed:
# Check how many times GitHubIssuePreview might render and in what context
rg -n "GitHubIssuePreview\|iframe" apps/web/src/routes/_view/roadmap.tsx -B 2 -A 2Repository: fastrepl/hyprnote
Length of output: 43
🏁 Script executed:
# Find the roadmap file
fd "roadmap" --type fRepository: fastrepl/hyprnote
Length of output: 152
🏁 Script executed:
# Search for GitHubIssuePreview without specifying tsx type
rg -n "GitHubIssuePreview" -B 3 -A 3Repository: fastrepl/hyprnote
Length of output: 1026
🏁 Script executed:
# Check repository structure
git ls-files apps/web/src/routes/ | head -20Repository: fastrepl/hyprnote
Length of output: 906
🏁 Script executed:
# Read the complete GitHubIssuePreview function and surrounding context
sed -n '340,380p' apps/web/src/routes/_view/roadmap.tsxRepository: fastrepl/hyprnote
Length of output: 1149
🏁 Script executed:
# Check if there's any lazy loading or optimization patterns elsewhere in the file
rg -n "lazy\|defer\|Suspense" apps/web/src/routes/_view/roadmap.tsxRepository: fastrepl/hyprnote
Length of output: 43
🏁 Script executed:
# Understand the data structure - check how many github issues might be attached per item
rg -n "githubIssues\|github_issues" apps/web/src/routes/_view/roadmap.tsx -B 2 -A 2Repository: fastrepl/hyprnote
Length of output: 43
GitHub iframe previews will be blocked by security headers and won't render; prefer lighter alternatives or API-based preview.
The web search confirmed that GitHub sends both X-Frame-Options and CSP frame-ancestors headers that deny framing of github.com pages. The only supported embedding mechanisms are the Gist embed script endpoint and GitHub Pages preview endpoints — direct issue page embedding is not allowed.
The current implementation attempts to embed full issue pages via iframe src={url} (line 365), which will fail silently with a blank iframe or security error. Additionally:
- No lazy loading: Each iframe is a 400px-height resource loaded immediately, and
item.githubIssuescan contain multiple URLs, multiplying the performance impact. - No fallback UI: Users see nothing when the embed is blocked, degrading experience.
- Permissive sandbox: The
allow-same-origin allow-scriptsattributes don't override browser security policies.
Recommended paths forward:
- Remove the iframe entirely and rely on the "View on GitHub" link + metadata (simplest).
- Fetch lightweight issue data via the GitHub API (title, state, comment count) and render as a card preview.
- At minimum, add
loading="lazy"and render only the first 1–2 previews per item to contain performance impact.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
apps/web/src/routes/_view/roadmap/$slug.tsx (1)
48-70: Consider movingstatusConfigoutside the component.This static configuration object is recreated on every render. Moving it to module scope avoids unnecessary allocations.
+const statusConfig = { + done: { + label: "Done", + icon: "mdi:check-circle", + iconColor: "text-green-600", + bgColor: "bg-green-50", + borderColor: "border-green-200", + }, + "in-progress": { + label: "In Progress", + icon: "mdi:progress-clock", + iconColor: "text-blue-600", + bgColor: "bg-blue-50", + borderColor: "border-blue-200", + }, + todo: { + label: "To Do", + icon: "mdi:calendar-clock", + iconColor: "text-neutral-400", + bgColor: "bg-neutral-50", + borderColor: "border-neutral-200", + }, +}; + function Component() { const { item } = Route.useLoaderData(); - - const statusConfig = { - done: { ... }, - ... - };apps/web/src/routes/_view/roadmap/index.tsx (2)
38-49: Consider simplifying or memoizinggetRoadmapItems.This function creates new array and objects on every call, and is invoked on each render (line 52). Since
allRoadmapsis static, you can simplify by:
- Using
allRoadmapsdirectly with a type assertion, or- Memoizing the result at module scope
-function getRoadmapItems(): RoadmapItem[] { - return allRoadmaps.map((item) => ({ - slug: item.slug, - title: item.title, - status: item.status, - labels: item.labels || [], - githubIssues: item.githubIssues || [], - created: item.created, - updated: item.updated, - mdx: item.mdx, - })); -} +const roadmapItems: RoadmapItem[] = allRoadmaps.map((item) => ({ + slug: item.slug, + title: item.title, + status: item.status as RoadmapStatus, + labels: item.labels || [], + githubIssues: item.githubIssues || [], + created: item.created, + updated: item.updated, + mdx: item.mdx, +}));Then in Component:
function Component() { - const items = getRoadmapItems(); + const items = roadmapItems;
245-248: Consider extractingmobileLimitas a named constant.For consistency with
DEFAULT_VISIBLE_ITEMS(line 36), consider extracting this magic number.const DEFAULT_VISIBLE_ITEMS = 5; +const MOBILE_VISIBLE_ITEMS = 3; // ... function ColumnSection({ ... }) { const [showAll, setShowAll] = useState(false); - const mobileLimit = 3; - const visibleItems = showAll ? items : items.slice(0, mobileLimit); - const hasMore = items.length > mobileLimit; + const visibleItems = showAll ? items : items.slice(0, MOBILE_VISIBLE_ITEMS); + const hasMore = items.length > MOBILE_VISIBLE_ITEMS;
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
apps/web/src/routeTree.gen.tsis excluded by!**/*.gen.ts
📒 Files selected for processing (2)
apps/web/src/routes/_view/roadmap/$slug.tsx(1 hunks)apps/web/src/routes/_view/roadmap/index.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, usecn(import from@hypr/utils). It is similar toclsx. Always pass an array and split by logical grouping.
Usemotion/reactinstead offramer-motion.
Files:
apps/web/src/routes/_view/roadmap/$slug.tsxapps/web/src/routes/_view/roadmap/index.tsx
🔇 Additional comments (8)
apps/web/src/routes/_view/roadmap/$slug.tsx (3)
1-8: LGTM!Imports are well-organized and follow the coding guidelines. The
cnutility is correctly imported from@hypr/utils.
10-43: LGTM!Route definition is well-structured with proper 404 handling via
notFound()and comprehensive SEO meta tags.
74-187: LGTM!Well-structured component with proper semantic HTML (
article,header,footer,timewithdateTime), responsive design, and correct usage of thecnutility with array syntax as per coding guidelines.apps/web/src/routes/_view/roadmap/index.tsx (5)
1-7: LGTM!Imports are appropriate. The
Linkcomponent is correctly used for internal navigation to the detail pages.
9-21: LGTM!Route definition with appropriate meta tags for SEO. Since
allRoadmapsis static content-collections data, client-side access is acceptable.
51-103: LGTM!Component structure is clean with proper responsive handling. Both
KanbanViewandColumnVieware rendered with CSS-based visibility toggling, which is a standard responsive pattern.
105-199: LGTM!Kanban view implementation is solid. The
statusprop is appropriately used for column border color styling. State management withuseStatefor the show more/less toggle is appropriate for this simple UI interaction.
284-338: LGTM!
RoadmapCardcorrectly usesLinkfor internal navigation to detail pages. The label truncation logic with "+N more" indicator provides good UX for items with many labels.
Summary
Replaces the hardcoded roadmap page with a dynamic view that fetches issues from the GitHub repository. The page now displays a kanban-style layout on wide viewports (lg: breakpoint) and a stacked column layout on narrow viewports, with item limits per section to keep the page manageable.
Key changes:
enhancement,feature, orimprovementfrom the GitHub APIUpdates since last revision
statusprop fromColumnSectioncomponentReview & Testing Checklist for Human
Linkcomponent is used with external GitHub URLs - verify links open correctly in new tabsTest plan: Run
pnpm -F web devand navigate to/roadmap. Test on both desktop (wide) and mobile (narrow) viewports. Verify issues load from GitHub, categories are correct, and expand/collapse works.Notes