-
Notifications
You must be signed in to change notification settings - Fork 432
Add SEO-ready pages, template pages with OG images, and shortcuts collection #1968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SEO-ready pages, template pages with OG images, and shortcuts collection #1968
Conversation
- Add comprehensive OG meta tags (og:title, og:description, og:type, og:url) to both pages - Expand self-hosting.tsx from a simple 'Coming Soon' page to a full landing page with: - Why self-host section (data sovereignty, compliance, air-gapped, customization) - Self-hosted vs Cloud comparison - Deployment options (Docker, Kubernetes, bare metal, private cloud, air-gapped, on-premises) - What you can self-host (transcription server, LLM inference, data storage) - Enterprise features (HIPAA/SOC 2, SSO/LDAP, analytics, support) - Open source foundation section - CTA section with contact sales link - Update local-ai.tsx meta description to include more SEO keywords - Both pages now ready for landing visitors with specific keywords and backlinking from blogs 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. |
|
The latest updates on your projects. Learn more about Argos notifications ↗︎ Awaiting the start of a new Argos build… |
- Create /free page showcasing Hyprnote's value proposition for SEO - Add /company-handbook route with sidebar navigation and content layout - Create handbook content collection in content-collections.ts - Add handbook content from company handbook document: - About section (What Hyprnote is, How it was born, Team, Why important, Vision) - How we work section (Work styles, Communication, Customer feedback) - Who we want section (Core traits, Hard skills, Founders example) - Go to market section (Customers, Value, Discovery, Communication, Pricing) - Communication section (Why it matters, North star, Principles) Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
📝 WalkthroughWalkthroughAdds Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Router as React Router
participant HandbookRoute as Handbook Route
participant Collections as Content Collections
participant MDX as MDX Compiler
participant Layout as HandbookLayout
User->>Router: Navigate to /company-handbook/<splat>
Router->>HandbookRoute: match dynamic handbook route
HandbookRoute->>HandbookRoute: beforeLoad (validate/redirect)
HandbookRoute->>Collections: loader -> resolve doc by slug/_splat
Collections-->>HandbookRoute: return doc { mdx, toc, slug, order, meta }
HandbookRoute->>Layout: render HandbookLayout with doc
Layout->>MDX: render compiled MDX to React
MDX-->>Layout: rendered content
Layout-->>User: display article, sidebar TOC, navigation
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)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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). (1)
Comment |
There was a problem hiding this 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
🧹 Nitpick comments (10)
apps/web/src/routes/_view/free.tsx (1)
233-285: Consider using semantic table elements for accessibility.The comparison grid uses
divelements withgrid-cols-3. For better screen reader support, consider using semantic<table>,<thead>,<tbody>,<tr>,<th>, and<td>elements. This helps assistive technologies understand the data relationships.apps/web/src/routes/_view/product/local-ai.tsx (1)
16-26: Consider adding og:image for enhanced social sharing.The Open Graph meta tags are well-implemented with appropriate title, description, type, and URL. However, an
og:imagetag would significantly improve how this page appears when shared on social media platforms (Twitter, LinkedIn, Slack, etc.).Add an og:image meta tag:
{ property: "og:type", content: "website" }, { property: "og:url", content: "https://hyprnote.com/product/local-ai", }, + { + property: "og:image", + content: "https://hyprnote.com/images/og/local-ai.png", + }, ],apps/web/content/handbook/who-we-want/2.founders-example.mdx (1)
7-7: Consider varying sentence structure for improved readability.Three consecutive sentences begin with "We," which static analysis flagged. While this repetition might be intentional for emphasis, varying the structure could enhance readability.
Example alternative:
-We hold ourselves to the same standards we expect from others. We're not perfect, but we try to embody grit, honesty, and sharpness every day. We expect future teammates to do the same — and to call us out when we fall short. +We hold ourselves to the same standards we expect from others. Though not perfect, we try to embody grit, honesty, and sharpness every day. Future teammates are expected to do the same — and to call us out when we fall short.apps/web/content/handbook/how-we-work/1.communication-and-focus.mdx (1)
7-15: Minor wording tweak for the “open documentation culture” bulletThe last bullet reads a bit awkwardly; adding “that” smooths it out:
-**Open documentation culture** we're still building, so everyone can contribute and learn. +**Open documentation culture** that we're still building, so everyone can contribute and learn.apps/web/src/routes/_view/company-handbook/$.tsx (1)
7-45: Make loader robust to missing docs and avoiddoc!non‑null assertionThe route logic overall looks good (normalization, section default redirects, and SEO meta), but the loader currently assumes
docis always found:return { doc: doc! };If
allHandbooksor slugs ever drift from whatbeforeLoadexpects, this becomes a runtime error instead of a controlled redirect/404. You can tighten this up and de‑duplicate the lookup logic a bit:- loader: async ({ params }) => { - const splat = params._splat || ""; - const normalizedSplat = splat.replace(/\/$/, ""); - - let doc = allHandbooks.find((doc) => doc.slug === normalizedSplat); - if (!doc) { - doc = allHandbooks.find((doc) => doc.slug === `${normalizedSplat}/index`); - } - - return { doc: doc! }; - }, + loader: async ({ params }) => { + const splat = params._splat || ""; + const normalizedSplat = splat.replace(/\/$/, ""); + + const doc = + allHandbooks.find((doc) => doc.slug === normalizedSplat) ?? + allHandbooks.find((doc) => doc.slug === `${normalizedSplat}/index`); + + if (!doc) { + // Fallback to the canonical intro page instead of throwing at runtime. + throw redirect({ + to: "/company-handbook/$", + params: { _splat: "about/what-hyprnote-is" }, + }); + } + + return { doc }; + },Optionally, you could also extract the slug‑normalization and doc‑lookup into a small helper so
beforeLoadandloadershare the same behavior.apps/web/src/routes/_view/company-handbook/index.tsx (1)
3-9: Consider importing default page from structure.ts.The default splat
"about/what-hyprnote-is"is duplicated here and instructure.ts. For single source of truth:import { createFileRoute, redirect } from "@tanstack/react-router"; +import { handbookStructure } from "./structure"; export const Route = createFileRoute("/_view/company-handbook/")({ beforeLoad: () => { throw redirect({ to: "/company-handbook/$", - params: { _splat: "about/what-hyprnote-is" }, + params: { _splat: handbookStructure.defaultPages.about }, }); }, });apps/web/src/routes/_view/product/self-hosting.tsx (1)
481-490: Use internalLinkcomponent for/foundersroute.The "Contact sales" button uses an absolute URL with
<a>tag, but/foundersappears to be an internal route. UsingLinkenables client-side navigation and prefetching.- <a - href="https://hyprnote.com/founders" + <Link + to="/founders" className={cn([ "px-8 py-3 text-base font-medium rounded-full", "bg-linear-to-t from-stone-600 to-stone-500 text-white", "hover:scale-105 active:scale-95 transition-transform", ])} > Contact sales - </a> + </Link>apps/web/src/routes/_view/company-handbook/route.tsx (1)
86-98: Usecnutility for conditional classNames.Per coding guidelines, when there are classNames with conditional logic, use
cnfrom@hypr/utils.+import { cn } from "@hypr/utils"; + // In the Link component: - className={`block px-3 py-1.5 text-sm rounded-sm transition-colors ${ - currentSlug === doc.slug - ? "bg-neutral-100 text-stone-600 font-medium" - : "text-neutral-600 hover:text-stone-600 hover:bg-neutral-50" - }`} + className={cn([ + "block px-3 py-1.5 text-sm rounded-sm transition-colors", + currentSlug === doc.slug + ? "bg-neutral-100 text-stone-600 font-medium" + : "text-neutral-600 hover:text-stone-600 hover:bg-neutral-50", + ])}Based on learnings,
cnshould be used with an array split by logical grouping.apps/web/src/routes/_view/company-handbook/-components.tsx (1)
221-233: UseLinkcomponent for internal/foundersroute.The "Talk to us" CTA uses
<a href="/founders">withtarget="_blank", but/foundersis an internal route. This should use theLinkcomponent for client-side navigation and shouldn't open in a new tab.- <a - href="/founders" - target="_blank" - rel="noopener noreferrer" + <Link + to="/founders" className={cn([ "group px-4 h-9 flex items-center justify-center text-sm w-full", "bg-linear-to-t from-stone-600 to-stone-500 text-white rounded-full", "hover:scale-[102%] active:scale-[98%]", "transition-all", ])} > Talk to us - </a> + </Link>apps/web/content-collections.ts (1)
404-463: Handbook collection implementation looks solid; duplication withdocstransform could be abstractedThe
handbookcollection schema and transform are consistent with existing patterns:
- MDX compilation and rehype/remark plugins match other collections.
toc,slug,sectionFolder,isIndex, andorderare derived identically todocs, which should integrate cleanly with handbook routes.- Adding
handbookto thecollectionsarray is correct and keeps config cohesive.Given
docsandhandbooknow share nearly identical transform logic, you might later extract a small shared helper (e.g.,buildMdxDocWithToc(document, context)) to reduce duplication and keep future changes (like slug rules or heading levels) in one place, but this is optional and not blocking.Also applies to: 475-475
📜 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 (29)
apps/web/content-collections.ts(2 hunks)apps/web/content/handbook/about/0.what-hyprnote-is.mdx(1 hunks)apps/web/content/handbook/about/1.how-hyprnote-was-born.mdx(1 hunks)apps/web/content/handbook/about/2.the-team.mdx(1 hunks)apps/web/content/handbook/about/3.why-hyprnote-is-important.mdx(1 hunks)apps/web/content/handbook/about/4.where-were-heading.mdx(1 hunks)apps/web/content/handbook/communication/0.why-this-matters.mdx(1 hunks)apps/web/content/handbook/communication/1.our-north-star.mdx(1 hunks)apps/web/content/handbook/communication/2.principles.mdx(1 hunks)apps/web/content/handbook/go-to-market/0.customers.mdx(1 hunks)apps/web/content/handbook/go-to-market/1.value-we-provide.mdx(1 hunks)apps/web/content/handbook/go-to-market/2.how-customers-find-us.mdx(1 hunks)apps/web/content/handbook/go-to-market/3.how-we-talk-with-them.mdx(1 hunks)apps/web/content/handbook/go-to-market/4.how-we-price.mdx(1 hunks)apps/web/content/handbook/how-we-work/0.work-styles.mdx(1 hunks)apps/web/content/handbook/how-we-work/1.communication-and-focus.mdx(1 hunks)apps/web/content/handbook/how-we-work/2.customer-feedback.mdx(1 hunks)apps/web/content/handbook/who-we-want/0.core-traits.mdx(1 hunks)apps/web/content/handbook/who-we-want/1.hard-skills.mdx(1 hunks)apps/web/content/handbook/who-we-want/2.founders-example.mdx(1 hunks)apps/web/content/handbook/who-we-want/3.future-teammates.mdx(1 hunks)apps/web/src/routes/_view/company-handbook/$.tsx(1 hunks)apps/web/src/routes/_view/company-handbook/-components.tsx(1 hunks)apps/web/src/routes/_view/company-handbook/index.tsx(1 hunks)apps/web/src/routes/_view/company-handbook/route.tsx(1 hunks)apps/web/src/routes/_view/company-handbook/structure.ts(1 hunks)apps/web/src/routes/_view/free.tsx(1 hunks)apps/web/src/routes/_view/product/local-ai.tsx(1 hunks)apps/web/src/routes/_view/product/self-hosting.tsx(3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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/src/routes/_view/company-handbook/structure.tsapps/web/content-collections.ts
**/*.{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/company-handbook/structure.tsapps/web/src/routes/_view/product/local-ai.tsxapps/web/src/routes/_view/company-handbook/-components.tsxapps/web/src/routes/_view/company-handbook/$.tsxapps/web/src/routes/_view/company-handbook/index.tsxapps/web/src/routes/_view/product/self-hosting.tsxapps/web/src/routes/_view/company-handbook/route.tsxapps/web/content-collections.tsapps/web/src/routes/_view/free.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-24T16:32:19.706Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`), similar to `clsx`. Always pass an array and split by logical grouping.
Applied to files:
apps/web/src/routes/_view/product/self-hosting.tsx
📚 Learning: 2025-11-24T16:32:23.055Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : If there are many classNames with conditional logic, use `cn` (import from `hypr/utils`). It is similar to `clsx`. Always pass an array and split by logical grouping.
Applied to files:
apps/web/src/routes/_view/product/self-hosting.tsx
🧬 Code graph analysis (5)
apps/web/src/routes/_view/company-handbook/$.tsx (5)
apps/web/src/routes/_view/company-handbook/index.tsx (1)
Route(3-10)apps/web/src/routes/_view/company-handbook/route.tsx (1)
Route(12-14)apps/web/src/routes/_view/free.tsx (1)
Route(6-26)apps/web/src/routes/_view/company-handbook/structure.ts (1)
handbookStructure(1-10)apps/web/src/routes/_view/company-handbook/-components.tsx (1)
HandbookLayout(17-35)
apps/web/src/routes/_view/company-handbook/index.tsx (2)
apps/web/src/routes/_view/company-handbook/$.tsx (1)
Route(7-61)apps/web/src/routes/_view/company-handbook/route.tsx (1)
Route(12-14)
apps/web/src/routes/_view/product/self-hosting.tsx (1)
packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/company-handbook/route.tsx (3)
apps/web/src/routes/_view/company-handbook/$.tsx (1)
Route(7-61)apps/web/src/routes/_view/company-handbook/index.tsx (1)
Route(3-10)apps/web/src/routes/_view/company-handbook/structure.ts (1)
handbookStructure(1-10)
apps/web/src/routes/_view/free.tsx (2)
apps/web/src/routes/_view/company-handbook/route.tsx (1)
Route(12-14)packages/utils/src/cn.ts (1)
cn(20-22)
🪛 LanguageTool
apps/web/content/handbook/who-we-want/2.founders-example.mdx
[style] ~7-~7: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...grit, honesty, and sharpness every day. We expect future teammates to do the same ...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
apps/web/content/handbook/go-to-market/3.how-we-talk-with-them.mdx
[style] ~15-~15: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ving problems, not selling features. Be responsive — Treat every piece of fee...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[style] ~17-~17: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...every piece of feedback as valuable. Be transparent — Share our roadmap, ackn...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
apps/web/content/handbook/go-to-market/2.how-customers-find-us.mdx
[uncategorized] ~11-~11: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...k for itself. ## Discovery Channels Open source community — Our GitHub presence and c...
(EN_COMPOUND_ADJECTIVE_INTERNAL)
apps/web/content/handbook/about/4.where-were-heading.mdx
[grammar] ~6-~6: Ensure spelling is correct
Context: ...t tomorrow, it will be much more." --- Hyprnote today is a notetaker. But tomorrow, it ...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
apps/web/content/handbook/about/0.what-hyprnote-is.mdx
[grammar] ~6-~6: Ensure spelling is correct
Context: ...cy-first, local-first philosophy." --- Hyprnote is the first open-source AI notetaker f...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
[grammar] ~14-~14: Ensure spelling is correct
Context: ...your behalf. ## Simple and Accessible Hyprnote is simple, accessible, and platform-agn...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
apps/web/content/handbook/about/1.how-hyprnote-was-born.mdx
[grammar] ~6-~6: Ensure spelling is correct
Context: ...tion with existing meeting tools." --- Hyprnote was born from real frustration. ## The...
(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
⏰ 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). (7)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Redirect rules - hyprnote-storybook
- GitHub Check: Header rules - hyprnote
- GitHub Check: fmt
- GitHub Check: ci (macos, macos-14)
- GitHub Check: Header rules - hyprnote-storybook
- GitHub Check: Pages changed - hyprnote
🔇 Additional comments (31)
apps/web/content/handbook/who-we-want/1.hard-skills.mdx (1)
1-7: LGTM!Frontmatter is well-structured with required fields, and the content clearly conveys the message about hard skills expectations.
apps/web/content/handbook/go-to-market/2.how-customers-find-us.mdx (1)
1-17: LGTM!Content is well-structured with clear discovery channel descriptions. The static analysis hint about hyphenating "Open source" can be ignored here since it functions as a noun phrase naming the community type rather than a compound adjective.
apps/web/content/handbook/go-to-market/0.customers.mdx (1)
1-15: LGTM!Clear customer segmentation that aligns well with Hyprnote's product positioning around privacy and data control.
apps/web/content/handbook/go-to-market/1.value-we-provide.mdx (1)
1-17: LGTM!Value propositions are clearly articulated and consistent with Hyprnote's product positioning.
apps/web/content/handbook/communication/0.why-this-matters.mdx (1)
1-11: LGTM!Good introductory content that sets the tone for the communication section.
apps/web/content/handbook/about/1.how-hyprnote-was-born.mdx (1)
1-19: LGTM!Compelling origin story with clear founder perspectives. The static analysis hint about "Hyprnote" is a false positive — it's the product name.
apps/web/src/routes/_view/free.tsx (6)
6-26: LGTM! Well-structured SEO meta tags.The
head()function properly includes title, description, and Open Graph meta tags for social sharing. The OG URL correctly points to the canonical path.
112-116: Verify the "HIPAA-ready" claim accuracy.The healthcare use case claims "HIPAA-ready with local processing." HIPAA compliance involves more than local processing (e.g., BAAs, audit trails, encryption requirements). Ensure this claim is accurate or consider softening to "designed with privacy in mind for healthcare use cases" to avoid potential compliance misrepresentation.
137-183: LGTM!Clean hero section with proper responsive design and good use of the
cnutility following the coding guidelines pattern.
185-219: LGTM!Features section is well-implemented with clean mapping over the data array.
327-399: LGTM!External GitHub link properly includes
rel="noopener noreferrer"for security. Good balance of content and statistics.
119-135: LGTM!Clean component composition with logical section ordering.
apps/web/content/handbook/who-we-want/0.core-traits.mdx (1)
1-17: Well-structured handbook content.The content is clear, concise, and well-organized with proper frontmatter and logical sections. The three core traits are effectively communicated with practical context.
apps/web/src/routes/_view/product/local-ai.tsx (2)
14-26: Inconsistent AI summary detected.The AI-generated summary states this PR "introduces a company handbook feature," but the PR objectives clearly describe SEO updates for self-hosting and local-ai product pages. This file's changes (adding OG meta tags) align with the PR objectives, not the summary.
14-26: Good SEO metadata implementation.The enhanced meta description with specific keywords (Whisper, LLM models, meeting transcription) and the addition of Open Graph tags will improve search visibility and social sharing. The variation between the main description and OG description is intentional and appropriate for different contexts.
apps/web/content/handbook/communication/1.our-north-star.mdx (1)
1-7: Clear and actionable communication principle.The North Star principle is well-defined and provides concrete criteria (educate, inspire, or solve a problem) for content decisions.
apps/web/content/handbook/about/2.the-team.mdx (1)
1-17: Well-crafted team introduction.The founder profiles effectively highlight relevant expertise and technical contributions, while the shared values section provides cohesive context. The structure is clean and professional.
apps/web/content/handbook/about/3.why-hyprnote-is-important.mdx (1)
1-19: Strong positioning and value proposition.The content effectively communicates Hyprnote's differentiators and market positioning. The structure progresses logically from problem statement to differentiators to core belief.
apps/web/content/handbook/go-to-market/3.how-we-talk-with-them.mdx (1)
1-17: Clear communication principles with effective parallel structure.The content is well-organized with an honest introduction and four concise principles. The repetitive "Be [adjective]" structure (flagged by static analysis) is intentional parallelism that enhances clarity and memorability for a principles list.
apps/web/content/handbook/how-we-work/0.work-styles.mdx (1)
1-19: Thoughtful work culture documentation.The content effectively balances individual work style descriptions with an overarching cultural philosophy. It humanizes the founders while establishing expectations around workplace diversity and autonomy.
apps/web/content/handbook/who-we-want/3.future-teammates.mdx (1)
1-7: Future teammates copy reads well and is consistentFrontmatter and body text are clear, aligned with the “who-we-want” section, and don’t introduce any structural issues.
apps/web/content/handbook/go-to-market/4.how-we-price.mdx (1)
1-15: Confirm pricing/philosophy claims match the actual offeringThe copy makes strong commitments (“core product is free and open source”, “Hyprnote experience is free forever”). Please double-check with current pricing plans and roadmap owners that these statements are accurate and durable enough to be in the handbook; otherwise consider softening (e.g., “designed to be…”, “we aim to keep…”) or being more specific about what’s free.
apps/web/content/handbook/how-we-work/2.customer-feedback.mdx (1)
1-11: Customer feedback process description looks solidClear explanation of consolidating feedback into GitHub Issues as the single public source of truth; wording and structure look good.
apps/web/content/handbook/about/4.where-were-heading.mdx (1)
1-19: Vision/ambition section is well-framed and consistentAspirational language is clearly scoped as “Our Vision / Our Ambition”, and the copy reads cleanly; no changes needed.
apps/web/content/handbook/communication/2.principles.mdx (1)
1-21: Communication principles page is clear and structuredThe four principles are well-organized, easy to scan, and consistent with the rest of the handbook content.
apps/web/content/handbook/about/0.what-hyprnote-is.mdx (1)
1-15: Double-check the “first open‑source AI notetaker for the enterprise” claimThe copy positions Hyprnote as “the first open-source AI notetaker for the enterprise”. That’s a strong comparative claim; please confirm with product/marketing that this is accurate and that they’re comfortable with this positioning. If there’s any doubt or risk of future contradictions, consider softening to something like “one of the first” or focusing on the privacy/local‑first angle instead.
apps/web/src/routes/_view/company-handbook/structure.ts (1)
1-10: LGTM - Clean configuration structure.The handbook structure is well-organized. Note that consumers (like
LeftSidebarin route.tsx) transform section IDs to titles using only.charAt(0).toUpperCase() + sectionId.slice(1), which converts"how-we-work"to"How-we-work"rather than"How we work". This may be intentional if the content collection uses matching casing, but verify the sidebar displays section titles as expected.apps/web/src/routes/_view/product/self-hosting.tsx (2)
8-28: LGTM - Well-structured OG meta tags for SEO.The Open Graph metadata is properly configured with title, description, type, and canonical URL. This will improve social sharing previews.
76-83: Verify enterprise feature claims before launch.The page makes specific claims about compliance and enterprise features:
- "HIPAA, GDPR, SOC 2" readiness (Lines 80-81)
- "SSO & LDAP integration" with "SAML, OIDC, and Active Directory out of the box" (Lines 396-400)
Per the PR checklist, these should be verified for accuracy. If these features are roadmap items rather than currently available, consider adjusting the copy (e.g., "designed for" or "supports").
Also applies to: 379-401
apps/web/src/routes/_view/company-handbook/-components.tsx (1)
17-23: LGTM - Component structure is well-organized.The
HandbookLayoutcleanly composes header, content, footer, and sidebar. Usingdoc: anyis acceptable here to avoid creating unnecessary interfaces per coding guidelines.apps/web/src/routes/_view/company-handbook/route.tsx (1)
64-70: The review comment is incorrect — the section title transformation works correctly.The transformation
sectionId.charAt(0).toUpperCase() + sectionId.slice(1)converts:
"about"→"About"(matchessection: "About"in content) ✓"how-we-work"→"How-we-work"(matchessection: "How-we-work"in content) ✓"who-we-want"→"Who-we-want"(matchessection: "Who-we-want"in content) ✓"go-to-market"→"Go-to-market"(matchessection: "Go-to-market"in content) ✓"communication"→"Communication"(matchessection: "Communication"in content) ✓The handbook content files use hyphenated section names that align perfectly with the transformation output. There is no lookup failure occurring.
Likely an incorrect or invalid review comment.
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
There was a problem hiding this 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
🧹 Nitpick comments (2)
apps/web/src/routes/_view/company-handbook/structure.ts (1)
1-16: Structure looks good; consider narrowing thedefaultPagestypingThe mapping is coherent and all
sectionsentries are represented indefaultPages, so this is functionally solid.To make future edits safer, you could avoid the broad
Record<string, string>assertion and tie the keys directly to the sections array. For example:-export const handbookStructure = { - sections: [ - "about", - "how-we-work", - "who-we-want", - "go-to-market", - "communication", - ], - defaultPages: { - about: "about/what-hyprnote-is", - "how-we-work": "how-we-work/work-styles", - "who-we-want": "who-we-want/core-traits", - "go-to-market": "go-to-market/customers", - communication: "communication/why-this-matters", - } as Record<string, string>, -}; +const handbookSections = [ + "about", + "how-we-work", + "who-we-want", + "go-to-market", + "communication", +] as const; + +export const handbookStructure = { + sections: handbookSections, + defaultPages: { + about: "about/what-hyprnote-is", + "how-we-work": "how-we-work/work-styles", + "who-we-want": "who-we-want/core-traits", + "go-to-market": "go-to-market/customers", + communication: "communication/why-this-matters", + } satisfies Record<(typeof handbookSections)[number], string>, +};This keeps runtime behavior identical but ensures TypeScript enforces that every section has a default page and blocks extra keys.
apps/web/src/routes/_view/solution/legal.tsx (1)
93-241: Consider extracting shared section components (optional refactor).All 7 solution pages in this PR share nearly identical
HeroSection,FeaturesSection,UseCasesSection, andCTASectioncomponents — differing only in data/content. Consider extracting these into a sharedSolutionPageLayoutcomponent that accepts the page-specific data as props:// Example: shared solution page layout function SolutionPageLayout({ badge, title, description, features, useCases, secondaryCTA }: SolutionPageProps) { return ( <div className="..."> <HeroSection badge={badge} title={title} ... /> <FeaturesSection features={features} /> <UseCasesSection useCases={useCases} /> <CTASection /> </div> ); }This would reduce ~150 lines of duplicate code per file and make future styling updates easier. However, this can be deferred if pages are expected to diverge significantly.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
apps/web/src/routes/_view/company-handbook/structure.ts(1 hunks)apps/web/src/routes/_view/solution/customer-success.tsx(1 hunks)apps/web/src/routes/_view/solution/field-engineering.tsx(1 hunks)apps/web/src/routes/_view/solution/government.tsx(1 hunks)apps/web/src/routes/_view/solution/healthcare.tsx(1 hunks)apps/web/src/routes/_view/solution/legal.tsx(1 hunks)apps/web/src/routes/_view/solution/media.tsx(1 hunks)apps/web/src/routes/_view/solution/project-management.tsx(1 hunks)apps/web/src/routes/_view/solution/recruiting.tsx(1 hunks)apps/web/src/routes/_view/solution/sales.tsx(1 hunks)
🧰 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/solution/field-engineering.tsxapps/web/src/routes/_view/company-handbook/structure.tsapps/web/src/routes/_view/solution/customer-success.tsxapps/web/src/routes/_view/solution/sales.tsxapps/web/src/routes/_view/solution/legal.tsxapps/web/src/routes/_view/solution/recruiting.tsxapps/web/src/routes/_view/solution/media.tsxapps/web/src/routes/_view/solution/project-management.tsxapps/web/src/routes/_view/solution/government.tsxapps/web/src/routes/_view/solution/healthcare.tsx
**/*.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/src/routes/_view/company-handbook/structure.ts
🧬 Code graph analysis (6)
apps/web/src/routes/_view/solution/sales.tsx (3)
apps/web/src/routes/_view/product/opensource.tsx (1)
Route(3-5)apps/web/src/routes/_view/security.tsx (1)
Route(3-5)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/legal.tsx (2)
apps/web/src/routes/_view/solution/sales.tsx (1)
Route(6-29)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/recruiting.tsx (7)
apps/web/src/routes/_view/solution/customer-success.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/government.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/legal.tsx (1)
Route(6-29)apps/web/src/routes/_view/solution/media.tsx (1)
Route(6-29)apps/web/src/routes/_view/solution/project-management.tsx (1)
Route(6-32)apps/web/src/routes/_view/solution/sales.tsx (1)
Route(6-29)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/project-management.tsx (4)
apps/web/src/routes/_view/solution/sales.tsx (1)
Route(6-29)apps/web/src/routes/_view/product/opensource.tsx (1)
Route(3-5)apps/web/src/routes/_view/security.tsx (1)
Route(3-5)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/government.tsx (4)
apps/web/src/routes/_view/solution/sales.tsx (1)
Route(6-29)apps/web/src/routes/_view/product/opensource.tsx (1)
Route(3-5)apps/web/src/routes/_view/security.tsx (1)
Route(3-5)packages/utils/src/cn.ts (1)
cn(20-22)
apps/web/src/routes/_view/solution/healthcare.tsx (3)
apps/web/src/routes/_view/solution/legal.tsx (1)
Route(6-29)apps/web/src/routes/_view/solution/sales.tsx (1)
Route(6-29)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). (5)
- GitHub Check: fmt
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci (macos, macos-14)
🔇 Additional comments (21)
apps/web/src/routes/_view/solution/recruiting.tsx (4)
1-32: LGTM — Route and SEO metadata follow the established pattern.The route definition with
head()metadata is consistent with other solution pages in the codebase. SEO tags (title, description, og:*) are properly configured.
34-94: Data arrays are well-structured.The
featuresanduseCasesarrays provide clean, data-driven content for the page sections. Good use of consistent object shapes.
112-242: Section components are clean and follow established conventions.The
cnutility is used correctly with arrays as per coding guidelines. The responsive design withsm:,md:,lg:breakpoints is appropriate.
96-110: <call_tool>
#!/bin/bashCheck the ACTUAL installed Tailwind version by looking at node_modules
echo "=== Checking actual Tailwind version installed ==="
cat apps/web/node_modules/tailwindcss/package.json 2>/dev/null | grep '"version"' || echo "Checking root node_modules..."
cat node_modules/tailwindcss/package.json 2>/dev/null | grep '"version"' || echo "Cannot determine installed version"Also check the CSS syntax - the @import and @theme are Tailwind v4 features
echo -e "\n=== Checking if bg-linear might be defined in @theme or elsewhere ==="
grep -r "linear" apps/web/src/index.css apps/web/src/globals.css 2>/dev/nullSearch for any PostCSS plugins that might add bg-linear
echo -e "\n=== Looking for tailwind-scrollbar-hide version ==="
cat apps/web/node_modules/tailwind-scrollbar-hide/package.json 2>/dev/null | grep -E '"version"|"name"' || echo "Checking root..."
cat node_modules/tailwind-scrollbar-hide/package.json 2>/dev/null | grep -E '"version"|"name"'Search if there are any Tailwind v4 features being used
echo -e "\n=== Checking if @theme syntax is v3 or v4 ==="
echo "The syntax used in index.css:"
echo "- @import 'tailwindcss' (v4 syntax)"
echo "- @theme { --breakpoint-laptop: 72rem; } (v4 syntax)"
echo "- @plugin '@tailwindcss/typography' (v4 syntax)"
echo ""
echo "This indicates Tailwind v4, not v3"
</function_calls><function_calls>
#!/bin/bashThe CSS file uses @import and @theme which are v4 syntax
Let's verify by checking if these are valid in v3
Also search for where bg-linear-* gradient utilities might come from
echo "=== Checking what defines bg-linear utilities ==="
Search in all node_modules for tailwind-related packages
find . -path "/node_modules/tailwindcss/" -name ".js" -o -name ".json" 2>/dev/null | grep -i "gradient|linear" | head -5
Check if tailwind-scrollbar-hide or other plugins define bg-linear
echo -e "\n=== Checking tailwind-scrollbar-hide plugin ==="
find . -path "/node_modules/tailwind-scrollbar-hide/" -type f 2>/dev/null | head -10Most direct approach: search npm/web for bg-linear documentation
echo -e "\n=== Searching codebase for any documentation or comments about bg-linear ==="
rg "bg-linear" --type ts --type tsx --type js --type md -B5 2>/dev/null | head -50
</function_calls>Let me take a web search approach to verify if
bg-linear-to-bis valid in Tailwind v3 or v4:
<function_calls>
Tailwind CSS bg-linear-to-b gradient class valid syntax
</function_calls>apps/web/src/routes/_view/solution/field-engineering.tsx (1)
1-242: LGTM — Consistent implementation for field engineering solution page.The page follows the established pattern with appropriate content tailored for field engineers. The "Offline Capable" feature and link to
/product/local-aiare contextually relevant for the target audience.apps/web/src/routes/_view/solution/media.tsx (1)
1-240: LGTM — Media & Entertainment solution page is well-structured.The content is appropriately tailored for creative teams with relevant features like "Capture Creative Ideas" and "Protect Creative IP". SEO metadata is properly configured.
apps/web/src/routes/_view/solution/government.tsx (2)
42-46: Verify compliance claims per PR checklist.The feature mentions "FedRAMP, FISMA, and other government compliance requirements." As noted in the PR testing checklist, ensure these capability claims are accurate before publishing.
1-41: LGTM — Government solution page appropriately emphasizes security.The focus on local processing, self-hosting, and compliance is well-suited for government agency audiences. The secondary CTA linking to
/product/self-hostingis contextually appropriate.Also applies to: 47-244
apps/web/src/routes/_view/solution/sales.tsx (1)
1-242: LGTM — Sales solution page is well-crafted.The content is appropriately tailored for sales teams with relevant features like "Deal Intelligence" and "Sales Coaching Insights". SEO metadata is properly configured.
apps/web/src/routes/_view/solution/customer-success.tsx (1)
1-243: LGTM — Customer Success solution page is well-designed.The content effectively targets CS teams with relevant features like "Track Health Signals" and "Renewal Preparation". SEO metadata follows the established pattern.
apps/web/src/routes/_view/solution/legal.tsx (1)
1-241: LGTM — Legal solution page appropriately emphasizes confidentiality.The focus on attorney-client privilege, local AI processing, and self-hosting is well-suited for legal professionals. The secondary CTA linking to
/product/local-aireinforces the privacy-first messaging.apps/web/src/routes/_view/solution/project-management.tsx (5)
1-32: LGTM!The imports and route definition follow the established pattern from other solution routes. SEO metadata is complete with appropriate title, description, and Open Graph tags.
34-94: LGTM!The
featuresanduseCasesdata arrays are well-structured with consistent schema. The content appropriately targets project management workflows.
158-216: LGTM!The
FeaturesSectionandUseCasesSectionare well-implemented with responsive grid layouts and proper React keys.
218-242: LGTM!The CTA section is clean and follows the established pattern with proper
cn()usage.
96-156: The review comment is incorrect and should be resolved.The project uses Tailwind CSS v4.1.17, and
bg-linear-to-b,bg-linear-to-t, and related gradient classes are the correct syntax for Tailwind v4 (not v3). The code in lines 96-156 properly implements these classes for the project's version. No changes are required.apps/web/src/routes/_view/solution/healthcare.tsx (5)
8-31: Verify HIPAA-ready compliance claims.The meta description claims "HIPAA-ready AI meeting notes" (line 14). Per the PR testing checklist, this claim should be verified. Consider whether "HIPAA-ready" accurately reflects the product's current compliance posture, or if softer language like "designed with healthcare privacy in mind" would be more appropriate until formal compliance is established.
34-71: LGTM!The features array appropriately targets healthcare workflows with privacy-focused messaging. The "Self-Hosting Option" feature (lines 66-70) aligns with the self-hosting page being added in this PR.
73-110: LGTM!The use cases appropriately cover healthcare meeting scenarios. The Component structure is consistent with other solution pages.
126-130: Verify privacy claim accuracy.The hero section states "Your patient data never leaves your device" (lines 128-129). This claim should be verified to ensure accuracy across all usage scenarios. If the product offers both local and cloud processing options, consider clarifying the conditions under which data remains local.
159-245: LGTM!The
FeaturesSection,UseCasesSection, andCTASectionare well-implemented and consistent with the established pattern across solution pages.
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
There was a problem hiding this 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
🧹 Nitpick comments (4)
apps/web/src/routes/_view/templates/$slug.tsx (4)
10-21: Duplicate template lookup between beforeLoad and loader.The template is looked up twice using
allTemplates.find()in bothbeforeLoad(line 11) andloader(line 19). While the non-null assertion on line 20 is safe becausebeforeLoadguarantees the template exists (via redirect), this duplication is inefficient and violates DRY.Consider consolidating the lookup logic if the router API allows, or at minimum, add a comment explaining why the duplication is necessary.
22-26: Consider breaking the long URL construction into multiple lines.The
ogImageUrlconstruction on line 25 is quite long and hard to read at a glance. For better readability, consider breaking it into multiple lines or extracting the query parameter construction.Example refactor:
- const ogImageUrl = `https://hyprnote.com/og?type=templates&title=${encodeURIComponent(template.title)}&category=${encodeURIComponent(template.category)}${template.description ? `&description=${encodeURIComponent(template.description)}` : ""}`; + const params = new URLSearchParams({ + type: "templates", + title: template.title, + category: template.category, + }); + if (template.description) { + params.set("description", template.description); + } + const ogImageUrl = `https://hyprnote.com/og?${params.toString()}`;
51-72: Consider moving icon mappings to template data.The hardcoded icon mapping creates tight coupling between template titles and icon identifiers. If a template title changes, this code must be manually updated, which is error-prone and not scalable.
Consider adding an
iconfield to the template content-collection schema and storing the icon identifier directly in each template's frontmatter. This would eliminate the need for this mapping function and make the data more self-contained.Additionally, if you keep this mapping, consider moving the
iconMapobject outside the function to avoid recreating it on every call:+const TEMPLATE_ICON_MAP: Record<string, string> = { + "Daily Standup": "mdi:run-fast", + // ... rest of mappings +}; + function getIconForTemplate(title: string): string { - const iconMap: Record<string, string> = { - "Daily Standup": "mdi:run-fast", - // ... - }; - return iconMap[title] || "mdi:file-document"; + return TEMPLATE_ICON_MAP[title] || "mdi:file-document"; }
162-162: Consider splitting the long className for better readability.The
classNameon line 162 is over 400 characters and spans multiple concerns (prose styling, code styling, spacing, borders, background). While the coding guideline specifically mentions usingcn"with conditional logic" (which doesn't apply here), splitting this into logical groups would still improve readability.Example refactor:
- <article className="prose prose-stone prose-headings:font-serif prose-headings:font-semibold prose-h2:text-xl prose-h2:mt-8 prose-h2:mb-4 prose-a:text-stone-600 prose-a:underline prose-a:decoration-dotted hover:prose-a:text-stone-800 prose-code:bg-stone-50 prose-code:border prose-code:border-neutral-200 prose-code:rounded prose-code:px-1.5 prose-code:py-0.5 prose-code:text-sm prose-code:font-mono prose-code:text-stone-700 max-w-none p-6 border border-neutral-200 rounded-lg bg-stone-50/50"> + <article className={cn([ + // Base prose + "prose prose-stone max-w-none", + // Heading styles + "prose-headings:font-serif prose-headings:font-semibold", + "prose-h2:text-xl prose-h2:mt-8 prose-h2:mb-4", + // Link styles + "prose-a:text-stone-600 prose-a:underline prose-a:decoration-dotted", + "hover:prose-a:text-stone-800", + // Code styles + "prose-code:bg-stone-50 prose-code:border prose-code:border-neutral-200", + "prose-code:rounded prose-code:px-1.5 prose-code:py-0.5", + "prose-code:text-sm prose-code:font-mono prose-code:text-stone-700", + // Container styles + "p-6 border border-neutral-200 rounded-lg bg-stone-50/50", + ])}>
📜 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 (1)
apps/web/src/routes/_view/templates/$slug.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/templates/$slug.tsx
🧬 Code graph analysis (1)
apps/web/src/routes/_view/templates/$slug.tsx (1)
apps/web/src/components/download-button.tsx (1)
DownloadButton(5-21)
⏰ 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). (5)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci (macos, macos-14)
- GitHub Check: fmt
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
There was a problem hiding this 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
🧹 Nitpick comments (1)
apps/web/netlify/edge-functions/og.tsx (1)
241-257: Consider extracting a shared renderer to reduce duplication.
renderTemplatesTemplateis nearly identical torenderDocsTemplate(lines 223-239), differing only in the header text ("Meeting Templates" vs "Hyprnote Docs") and the parameter field name (categoryvssection).Consider extracting a shared renderer function:
+function renderCardTemplate( + headerText: string, + sectionOrCategory: string, + title: string, + description?: string +) { + return ( + <div style={{ width: '100%', height: '100%', paddingLeft: 56, paddingRight: 56, paddingTop: 55, paddingBottom: 55, background: 'linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-start', display: 'flex' }}> + <div style={{ justifyContent: 'flex-start', alignItems: 'center', gap: 12, display: 'flex' }}> + <img style={{ width: 48, height: 48 }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" /> + <div style={{ color: '#292524', fontSize: 36, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{headerText}</div> + </div> + <div style={{ alignSelf: 'stretch', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', gap: 12, display: 'flex' }}> + <div style={{ color: '#525252', fontSize: 32, fontFamily: 'IBM Plex Mono', fontWeight: '500', wordWrap: 'break-word' }}>{sectionOrCategory}</div> + <div style={{ alignSelf: 'stretch', color: '#292524', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{preventWidow(title)}</div> + {description && ( + <div style={{ alignSelf: 'stretch', color: '#525252', fontSize: 36, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word' }}>{description}</div> + )} + </div> + </div> + ); +} + function renderDocsTemplate(params: z.infer<typeof docsSchema>) { - return ( - <div style={{ width: '100%', height: '100%', paddingLeft: 56, paddingRight: 56, paddingTop: 55, paddingBottom: 55, background: 'linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-start', display: 'flex' }}> - <div style={{ justifyContent: 'flex-start', alignItems: 'center', gap: 12, display: 'flex' }}> - <img style={{ width: 48, height: 48 }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" /> - <div style={{ color: '#292524', fontSize: 36, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>Hyprnote Docs</div> - </div> - <div style={{ alignSelf: 'stretch', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', gap: 12, display: 'flex' }}> - <div style={{ color: '#525252', fontSize: 32, fontFamily: 'IBM Plex Mono', fontWeight: '500', wordWrap: 'break-word' }}>{params.section}</div> - <div style={{ alignSelf: 'stretch', color: '#292524', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{preventWidow(params.title)}</div> - {params.description && ( - <div style={{ alignSelf: 'stretch', color: '#525252', fontSize: 36, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word' }}>{params.description}</div> - )} - </div> - </div> - ); + return renderCardTemplate("Hyprnote Docs", params.section, params.title, params.description); } function renderTemplatesTemplate(params: z.infer<typeof templatesSchema>) { - return ( - <div style={{ width: '100%', height: '100%', paddingLeft: 56, paddingRight: 56, paddingTop: 55, paddingBottom: 55, background: 'linear-gradient(0deg, #FAFAF9 0%, #E7E5E4 100%)', overflow: 'hidden', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'flex-start', display: 'flex' }}> - <div style={{ justifyContent: 'flex-start', alignItems: 'center', gap: 12, display: 'flex' }}> - <img style={{ width: 48, height: 48 }} src="https://ijoptyyjrfqwaqhyxkxj.supabase.co/storage/v1/object/public/public_images/icons/stable-icon.png" /> - <div style={{ color: '#292524', fontSize: 36, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>Meeting Templates</div> - </div> - <div style={{ alignSelf: 'stretch', flexDirection: 'column', justifyContent: 'flex-start', alignItems: 'flex-start', gap: 12, display: 'flex' }}> - <div style={{ color: '#525252', fontSize: 32, fontFamily: 'IBM Plex Mono', fontWeight: '500', wordWrap: 'break-word' }}>{params.category}</div> - <div style={{ alignSelf: 'stretch', color: '#292524', fontSize: 60, fontFamily: 'Lora', fontWeight: '700', wordWrap: 'break-word' }}>{preventWidow(params.title)}</div> - {params.description && ( - <div style={{ alignSelf: 'stretch', color: '#525252', fontSize: 36, fontFamily: 'IBM Plex Mono', fontWeight: '400', wordWrap: 'break-word' }}>{params.description}</div> - )} - </div> - </div> - ); + return renderCardTemplate("Meeting Templates", params.category, params.title, params.description); }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/netlify/edge-functions/og.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/netlify/edge-functions/og.tsx
⏰ 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). (5)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: fmt
- GitHub Check: ci (macos, macos-14)
- GitHub Check: Pages changed - hyprnote
🔇 Additional comments (4)
apps/web/netlify/edge-functions/og.tsx (4)
6-17: LGTM! Well-structured schema definitions.The separation of
meetingSchemaandtemplatesSchemawith a discriminated union provides good type safety. ThetemplatesSchemafields (title, category, optional description) are appropriate for template metadata.Also applies to: 39-39
84-91: LGTM! Parsing logic is correct.The templates type parsing correctly extracts and validates the required fields, with proper handling of the optional description parameter.
283-286: LGTM! Handler routing is correct.The routing logic properly handles the new
templatestype and correctly falls back torenderMeetingTemplatefor the meeting type.
289-289: LGTM! Font loading logic correctly updated.Including
"templates"in theneedsCustomFontscheck is correct sincerenderTemplatesTemplateuses the Lora and IBM Plex Mono fonts.
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
There was a problem hiding this 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
🧹 Nitpick comments (1)
apps/web/content-collections.ts (1)
442-501: Consider extracting shared transform logic.The handbook collection transform duplicates nearly the entire docs collection transform (lines 131-191). Both perform identical operations: extractToc, compileMDX with the same plugins, path parsing, sectionFolder/isIndex/order computation, and slug generation.
Consider extracting a shared helper function to improve maintainability and ensure consistency when updating the transform logic.
Example refactor:
function createDocStyleTransform() { return async (document: any, context: any) => { const toc = extractToc(document.content); const mdx = await compileMDX(context, document, { remarkPlugins: [remarkGfm, mdxMermaid], rehypePlugins: [ rehypeSlug, [ rehypeAutolinkHeadings, { behavior: "wrap", properties: { className: ["anchor"], }, }, ], ], }); const pathParts = document._meta.path.split("/"); const fileName = pathParts.pop()!.replace(/\.mdx$/, ""); const sectionFolder = pathParts[0] || "general"; const isIndex = fileName === "index"; const orderMatch = fileName.match(/^(\d+)\./); const order = orderMatch ? parseInt(orderMatch[1], 10) : 999; const cleanFileName = fileName.replace(/^\d+\./, ""); const cleanPath = pathParts.length > 0 ? `${pathParts.join("/")}/${cleanFileName}` : cleanFileName; const slug = cleanPath; return { ...document, mdx, slug, sectionFolder, isIndex, order, toc, }; }; } // Then use it for both collections const docs = defineCollection({ // ... schema ... transform: createDocStyleTransform(), }); const handbook = defineCollection({ // ... schema ... transform: createDocStyleTransform(), });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
apps/web/content-collections.ts(1 hunks)apps/web/content/shortcuts/action-items.mdx(1 hunks)apps/web/content/shortcuts/customer-concerns.mdx(1 hunks)apps/web/content/shortcuts/decision-summary.mdx(1 hunks)apps/web/content/shortcuts/follow-up-email.mdx(1 hunks)apps/web/content/shortcuts/meeting-insights.mdx(1 hunks)apps/web/content/shortcuts/sales-feedback.mdx(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/content/shortcuts/customer-concerns.mdx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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
**/*.{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/content-collections.ts
⏰ 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). (5)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: fmt
- GitHub Check: ci (macos, macos-14)
🔇 Additional comments (6)
apps/web/content/shortcuts/meeting-insights.mdx (1)
1-24: LGTM! Well-structured shortcut content.The frontmatter and content sections are clear and consistent. The prompt is detailed with specific analysis points that will guide the AI effectively.
apps/web/content/shortcuts/follow-up-email.mdx (1)
1-24: LGTM! Clear and actionable email drafting guide.The prompt provides specific requirements for follow-up emails with appropriate structure and tone guidance.
apps/web/content/shortcuts/decision-summary.mdx (1)
1-24: LGTM! Comprehensive decision documentation guide.The prompt effectively covers all aspects of decision capture including context, reasoning, stakeholders, and dissenting opinions.
apps/web/content/shortcuts/action-items.mdx (1)
1-24: LGTM! Clear action item extraction guide.The prompt includes all necessary details (task, owner, deadline, dependencies) and flags urgent items appropriately.
apps/web/content/shortcuts/sales-feedback.mdx (1)
1-24: LGTM! Comprehensive sales coaching guide.The prompt covers all critical aspects of sales call analysis with actionable feedback focus.
apps/web/content-collections.ts (1)
404-440: LGTM! Clean shortcuts collection implementation.The schema appropriately captures shortcut metadata, and the transform follows the established pattern used by other collections.
Co-Authored-By: john@hyprnote.com <john@hyprnote.com>
There was a problem hiding this 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
🧹 Nitpick comments (4)
apps/web/src/routes/_view/company-handbook/-components.tsx (4)
17-23: Tightendoctyping to avoidanyand make required fields explicitBoth
HandbookLayoutandArticleHeaderacceptdoc: anywhile relying on fields likesectionFolder,title,summary,mdx,toc,created,updated, andauthor. If you have a generated/content-collections type for handbook docs, wiring that through here would:
- Prevent accidental usage with the wrong doc shape.
- Make fields like
sectionFolderandtocclearly required, avoiding potential runtime errors (e.g., calling.splitor accessing.tocon undefined).If a shared type exists (e.g., from
content-collections), prefer using it instead ofany; otherwise, a local minimal type for this layout would still be an improvement.Also applies to: 37-51
68-95: Consider clarifying date expectations or guarding against invalid dates
doc.created/doc.updatedare passed straight intonew Date(...)and formatted. If those values ever deviate from a valid ISO/date string, this will silently render"Invalid Date".If your handbook schema guarantees valid dates, this is fine; otherwise, you might:
- Validate/normalize dates at content-layer level, or
- Guard here (e.g., only render
<time>when!isNaN(new Date(doc.created).getTime())).Not blocking, but worth confirming the schema guarantees.
186-215: Maketochandling a bit more defensive to avoid accidental runtime errors
RightSidebarassumestocis a non-empty array (toc.length > 0) andHandbookLayoutpassesdoc.tocdirectly. Ifdoc.tocwere everundefinedornull(e.g., for a doc without generated TOC), this would throw.Even if the current content schema always provides
toc: [], a small guard would make this more robust, e.g.:- <RightSidebar toc={doc.toc} /> + <RightSidebar toc={doc.toc ?? []} />and/or adjust the prop type to
toc?: Array<...>and default to[]insideRightSidebar.
217-235: CTA card implementation is clean; minor note on routing choiceThe “Talk to us” CTA uses an
<a>withhref="/founders"plustarget="_blank"andrel="noopener noreferrer", which is correct technically. If you don’t specifically want a new tab, you could switch to<Link to="/founders">for client-side navigation; otherwise this is fine as-is.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/web/src/routes/_view/company-handbook/-components.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/company-handbook/-components.tsx
🧬 Code graph analysis (1)
apps/web/src/routes/_view/company-handbook/-components.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). (5)
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
- GitHub Check: ci (macos, macos-14)
- GitHub Check: fmt
🔇 Additional comments (2)
apps/web/src/routes/_view/company-handbook/-components.tsx (2)
100-117: MDX rendering setup and component mapping look solidThe MDXContent usage with a curated components map and well-tuned prose classes is clean and composable. No functional issues stand out here.
120-183: Footer structure and links look correct and semantically appropriateBack-link, legal links via
Link, and external social links (GitHub, Discord, Twitter/X) are wired correctly withtarget="_blank"andrel="noopener noreferrer". No changes needed from a correctness or UX standpoint.
There was a problem hiding this 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
📜 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/content-collections.ts(2 hunks)apps/web/src/routes/_view/templates/$slug.tsx(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/src/routes/_view/templates/$slug.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.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
**/*.{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/content-collections.ts
⏰ 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). (1)
- GitHub Check: fmt
🔇 Additional comments (2)
apps/web/content-collections.ts (2)
404-440: LGTM! Shortcuts collection is well-structured.The schema and transform logic follow established patterns. The absence of TOC extraction is appropriate for short-form shortcut content.
Note: Per the PR objectives, no UI/route has been implemented for this collection yet.
554-554: LGTM! Collections properly exported.Both
shortcutsandhandbookcollections are correctly added to the configuration.Also applies to: 558-558
| const handbook = defineCollection({ | ||
| name: "handbook", | ||
| directory: "content/handbook", | ||
| include: "**/*.mdx", | ||
| exclude: "AGENTS.md", | ||
| schema: z.object({ | ||
| title: z.string(), | ||
| section: z.string(), | ||
| summary: z.string().optional(), | ||
| author: z.string().optional(), | ||
| created: z.string().optional(), | ||
| updated: z.string().optional(), | ||
| }), | ||
| transform: async (document, context) => { | ||
| const toc = extractToc(document.content); | ||
|
|
||
| const mdx = await compileMDX(context, document, { | ||
| remarkPlugins: [remarkGfm, mdxMermaid], | ||
| rehypePlugins: [ | ||
| rehypeSlug, | ||
| [ | ||
| rehypeAutolinkHeadings, | ||
| { | ||
| behavior: "wrap", | ||
| properties: { | ||
| className: ["anchor"], | ||
| }, | ||
| }, | ||
| ], | ||
| ], | ||
| }); | ||
|
|
||
| const pathParts = document._meta.path.split("/"); | ||
| const fileName = pathParts.pop()!.replace(/\.mdx$/, ""); | ||
|
|
||
| const sectionFolder = pathParts[0] || "general"; | ||
|
|
||
| const isIndex = fileName === "index"; | ||
|
|
||
| const orderMatch = fileName.match(/^(\d+)\./); | ||
| const order = orderMatch ? parseInt(orderMatch[1], 10) : 999; | ||
|
|
||
| const cleanFileName = fileName.replace(/^\d+\./, ""); | ||
| const cleanPath = | ||
| pathParts.length > 0 | ||
| ? `${pathParts.join("/")}/${cleanFileName}` | ||
| : cleanFileName; | ||
| const slug = cleanPath; | ||
|
|
||
| return { | ||
| ...document, | ||
| mdx, | ||
| slug, | ||
| sectionFolder, | ||
| isIndex, | ||
| order, | ||
| toc, | ||
| }; | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Extract shared transform logic with docs collection.
The handbook transform (lines 499-544) duplicates nearly all the logic from the docs collection transform (lines 145-189). Both compute toc, pathParts, fileName, sectionFolder, isIndex, order, and slug identically.
Consider extracting the shared transform logic into a helper function:
+function createDocsLikeTransform() {
+ return async (document: any, context: any) => {
+ const toc = extractToc(document.content);
+
+ const mdx = await compileMDX(context, document, {
+ remarkPlugins: [remarkGfm, mdxMermaid],
+ rehypePlugins: [
+ rehypeSlug,
+ [
+ rehypeAutolinkHeadings,
+ {
+ behavior: "wrap",
+ properties: {
+ className: ["anchor"],
+ },
+ },
+ ],
+ ],
+ });
+
+ const pathParts = document._meta.path.split("/");
+ const fileName = pathParts.pop()!.replace(/\.mdx$/, "");
+
+ const sectionFolder = pathParts[0] || "general";
+
+ const isIndex = fileName === "index";
+
+ const orderMatch = fileName.match(/^(\d+)\./);
+ const order = orderMatch ? parseInt(orderMatch[1], 10) : 999;
+
+ const cleanFileName = fileName.replace(/^\d+\./, "");
+ const cleanPath =
+ pathParts.length > 0
+ ? `${pathParts.join("/")}/${cleanFileName}`
+ : cleanFileName;
+ const slug = cleanPath;
+
+ return {
+ ...document,
+ mdx,
+ slug,
+ sectionFolder,
+ isIndex,
+ order,
+ toc,
+ };
+ };
+}Then use it in both collections:
const docs = defineCollection({
name: "docs",
directory: "content/docs",
include: "**/*.mdx",
exclude: "AGENTS.md",
schema: z.object({
title: z.string(),
section: z.string(),
summary: z.string().optional(),
category: z.string().optional(),
author: z.string().optional(),
created: z.string().optional(),
updated: z.string().optional(),
}),
- transform: async (document, context) => {
- // ... duplicated logic
- },
+ transform: createDocsLikeTransform(),
});
const handbook = defineCollection({
name: "handbook",
directory: "content/handbook",
include: "**/*.mdx",
exclude: "AGENTS.md",
schema: z.object({
title: z.string(),
section: z.string(),
summary: z.string().optional(),
author: z.string().optional(),
created: z.string().optional(),
updated: z.string().optional(),
}),
- transform: async (document, context) => {
- // ... duplicated logic
- },
+ transform: createDocsLikeTransform(),
});Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 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 (1)
apps/web/src/routes/_view/templates/$slug.tsx(2 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/templates/$slug.tsx
⏰ 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: fmt
- GitHub Check: Redirect rules - hyprnote
- GitHub Check: Header rules - hyprnote
- GitHub Check: Pages changed - hyprnote
🔇 Additional comments (1)
apps/web/src/routes/_view/templates/$slug.tsx (1)
7-7: LGTM: Import follows coding guidelines.The
cnutility import is correctly used later in the file (lines 272-277) with an array of classNames split by logical grouping, which aligns with the coding guidelines.
| const url = `https://hyprnote.com/templates/${template.slug}`; | ||
|
|
||
| const ogImageUrl = `https://hyprnote.com/og?type=templates&title=${encodeURIComponent(template.title)}&category=${encodeURIComponent(template.category)}${template.description ? `&description=${encodeURIComponent(template.description)}` : ""}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded production URL affects all environments.
Both url and ogImageUrl use the hardcoded production domain https://hyprnote.com, which means OG images and canonical URLs will point to production even in development and staging environments. This makes local testing of OG images difficult and breaks environment parity.
Additionally, line 24 is quite long and difficult to read.
Consider extracting the base URL to a constant or environment variable and refactoring the URL construction:
+const baseUrl = process.env.PUBLIC_URL || 'https://hyprnote.com';
+
export const Route = createFileRoute("/_view/templates/$slug")({
component: Component,
loader: async ({ params }) => {
const template = allTemplates.find(
(template) => template.slug === params.slug,
);
if (!template) {
throw notFound();
}
return { template };
},
head: ({ loaderData }) => {
const { template } = loaderData!;
- const url = `https://hyprnote.com/templates/${template.slug}`;
+ const url = `${baseUrl}/templates/${template.slug}`;
- const ogImageUrl = `https://hyprnote.com/og?type=templates&title=${encodeURIComponent(template.title)}&category=${encodeURIComponent(template.category)}${template.description ? `&description=${encodeURIComponent(template.description)}` : ""}`;
+ const ogImageParams = new URLSearchParams({
+ type: 'templates',
+ title: template.title,
+ category: template.category,
+ });
+ if (template.description) {
+ ogImageParams.set('description', template.description);
+ }
+ const ogImageUrl = `${baseUrl}/og?${ogImageParams}`;
return {Committable suggestion skipped: line range outside the PR's diff.
| { title: `${template.title} - Meeting Template - Hyprnote` }, | ||
| { name: "description", content: template.description }, | ||
| { property: "og:title", content: `${template.title} Template` }, | ||
| { | ||
| property: "og:title", | ||
| content: `${template.title} - Meeting Template`, | ||
| }, | ||
| { property: "og:description", content: template.description }, | ||
| { property: "og:type", content: "website" }, | ||
| { property: "og:type", content: "article" }, | ||
| { property: "og:url", content: url }, | ||
| { name: "twitter:card", content: "summary" }, | ||
| { name: "twitter:title", content: `${template.title} Template` }, | ||
| { property: "og:image", content: ogImageUrl }, | ||
| { name: "twitter:card", content: "summary_large_image" }, | ||
| { | ||
| name: "twitter:title", | ||
| content: `${template.title} - Meeting Template`, | ||
| }, | ||
| { name: "twitter:description", content: template.description }, | ||
| { name: "twitter:image", content: ogImageUrl }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Open Graph protocol og:type article best practices for product pages
💡 Result:
Summary — do NOT use og:type=article for standard product pages. Use product (or website) and product:* properties; reserve article only for editorial/blog/news content about the product.
Recommended best practices for product pages
- Set og:type to product (or website for generic landing pages). Article is for blog/news/editorial only. [1][2]
- Include canonical og:url and og:site_name to avoid duplicate/share confusion. [1][3]
- Provide a large, high‑quality og:image (recommended 1200×630 px, 1.91:1) and include og:image:width / og:image:height and og:image:alt. [4][5]
- Add product-specific Open Graph properties (product:retailer_item_id, product:availability, product:price:amount / product:price:currency, product:product_link, etc.) when relevant so platforms and integrations can surface commerce data. [2][3]
- Keep og:title and og:description concise and aligned with on‑page content; test how they truncate on platforms. [4][5]
- Ensure meta tags are present in the server‑rendered HTML (social crawlers do not execute client JS). [5]
- Use the platform validators (Facebook Sharing Debugger, LinkedIn Post Inspector, Twitter Card Validator) to preview and force a rescrape after changes. [4]
- If the page includes editorial (reviews, press release, blog post) use og:type=article and add article:* fields (published_time, modified_time, author, tag). Otherwise do not mark product pages as article. [1][2]
Sources
- Open Graph / Facebook guidance and common og:type values (product, article, website). [1][3]
- Yoast developer docs: typical site mappings (article for posts, website/home, profile pages). [2]
- Example e‑commerce implementations and product:* usage (platform docs / examples). [3]
- OG image and general tag best practices (sizes, dimensions, validators). [4][5]
🏁 Script executed:
#!/bin/bash
# Get context about the template page to understand what it represents
head -50 apps/web/src/routes/_view/templates/\$slug.tsxRepository: fastrepl/hyprnote
Length of output: 1931
Change og:type to "product" or "website" — "article" is reserved for editorial content.
According to Open Graph best practices, og:type="article" should only be used for editorial, blog, or news content. For product and template showcase pages like this one, use og:type="product" (or "website" for generic landing pages) instead. This affects how social platforms categorize and display the page.
Consider also adding product-specific Open Graph properties (e.g., product:availability, product:price:amount) if commerce data is relevant.
🤖 Prompt for AI Agents
In apps/web/src/routes/_view/templates/$slug.tsx around lines 28 to 44, the Open
Graph meta currently sets og:type to "article" which is reserved for editorial
content; change the og:type value to either "product" (preferred for
templates/products) or "website" (for a generic landing page) by replacing the
property value, and optionally add product-specific OG properties (e.g.,
product:availability, product:price:amount, product:brand) where relevant to
improve social platform categorization and display.
Add SEO-ready pages, template pages with OG images, and shortcuts collection
Summary
This PR adds SEO-ready landing pages, a company handbook with docs-style layout, industry/role-specific solution pages, individual template detail pages with OG image support, and a new shortcuts content collection for AI assistant prompts.
self-hosting.tsx & local-ai.tsx:
New /free page:
New /company-handbook:
handbookcollection to content-collections.tsNew /solution/[slug] pages (9 pages):
New /templates/$slug pages (individual template detail pages):
/templates/{slug}with full OG image supporthttps://hyprnote.com/og?type=templates&title=...&category=...&description=.../templatesif template slug not foundOG image endpoint update (
apps/web/netlify/edge-functions/og.tsx):templatesSchemafortype=templatesparameter validationrenderTemplatesTemplate()function with same styling as docs OG imagestype=templatesrequests to the new render functionNew shortcuts content collection:
shortcutscollection to content-collections.ts for AI assistant promptsUpdates since last revision
@hyprnoteto@tryhyprnote(CodeRabbit review feedback)Review & Testing Checklist for Human
promptfield contains the system prompt for AI assistant. Confirm this structure works for the intended use case./og?type=templates&title=Daily%20Standup&category=Engineeringrenders correctly.Recommended test plan:
/templates/daily-standup- verify template detail page renders with icon, sections, and preview/company-handbook- verify redirect to first page, test sidebar navigation/free- scroll through all sections/solution/*page - verify content and iconsNotes