Development#123
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThis PR adds a Bytepay/Paymenter-backed billing product pipeline (fetching, JSON:API normalization, spec parsing, category plan builders, and a debug endpoint), replaces the instatus status proxy with a new status.nodebyte.host-backed API route, hook, and badge wired into Nodes/footer, plus minor services grid styling and a translations submodule bump. ChangesBilling-native pricing and product catalog
Estimated code review effort: 4 (Complex) | ~60 minutes Live node status monitoring replacing instatus proxy
Estimated code review effort: 3 (Moderate) | ~30 minutes Services grid styling and translations bump
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant NodesClient
participant useNodeStatus
participant StatusRoute as "/api/status"
participant StatusLib as "status.ts"
participant StatusAPI as "status.nodebyte.host"
NodesClient->>useNodeStatus: mount, call hook
useNodeStatus->>StatusRoute: fetch("/api/status")
StatusRoute->>StatusLib: fetchStatusSnapshot()
StatusLib->>StatusAPI: GET /api/v1/public/status
StatusAPI-->>StatusLib: monitors + heartbeats
StatusLib-->>StatusRoute: StatusSnapshot | null
StatusRoute-->>useNodeStatus: StatusApiResponse (monitors, overallStatus)
useNodeStatus-->>NodesClient: monitors, available, findMonitor
NodesClient->>NodesClient: resolveNodeState per node/location
loop every 60s
useNodeStatus->>StatusRoute: re-fetch status
end
sequenceDiagram
participant BillingService as "billing-service.ts"
participant Bytepay as "bytepay.ts"
participant PaymenterAPI as "Paymenter admin API"
participant SpecParser as "spec-parser.ts"
participant Page as "Debug/Plan route"
Page->>BillingService: getGamePlans/getDedicatedPlans/getVpsPlans(categorySlug)
BillingService->>Bytepay: fetchAllBillingProducts() (cached)
Bytepay->>PaymenterAPI: paginated GET products/categories
PaymenterAPI-->>Bytepay: JSON:API pages
Bytepay-->>BillingService: BillingProduct[]
BillingService->>SpecParser: parseDescriptionSpecs/parseProductName
SpecParser-->>BillingService: ParsedSpecs
BillingService-->>Page: PlanSpec[] (with dropped-product warnings)
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| let cpuModel: string | undefined | ||
| let hardware: ParsedSpecs["hardware"] | ||
|
|
||
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) |
| let hardware: ParsedSpecs["hardware"] | ||
|
|
||
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) |
| let hardware: ParsedSpecs["hardware"] | ||
|
|
||
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) |
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) | ||
| // Intel: Xeon, Core Ultra, Core i-series | ||
| const intelMatch = text.match(/Intel[®®]?\s+(?:Core[™™]?\s+Ultra\s+\d+(?:\s+\d+)?|Core[™™]?\s+i\d+[- ]\d+\w*|Xeon[®®]?(?:\s+\w+)*)/i) |
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) | ||
| // Intel: Xeon, Core Ultra, Core i-series | ||
| const intelMatch = text.match(/Intel[®®]?\s+(?:Core[™™]?\s+Ultra\s+\d+(?:\s+\d+)?|Core[™™]?\s+i\d+[- ]\d+\w*|Xeon[®®]?(?:\s+\w+)*)/i) |
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) | ||
| // Intel: Xeon, Core Ultra, Core i-series | ||
| const intelMatch = text.match(/Intel[®®]?\s+(?:Core[™™]?\s+Ultra\s+\d+(?:\s+\d+)?|Core[™™]?\s+i\d+[- ]\d+\w*|Xeon[®®]?(?:\s+\w+)*)/i) |
| const ryzenMatch = text.match(/AMD\s+Ryzen[™™]?\s+\d+(?:\s+(?:PRO\s+)?\d+\w*)?/i) | ||
| const ampereMatch = text.match(/Ampere[®®]?\s+Altra[®®]?(?:\s+ARM64)?/i) | ||
| // Intel: Xeon, Core Ultra, Core i-series | ||
| const intelMatch = text.match(/Intel[®®]?\s+(?:Core[™™]?\s+Ultra\s+\d+(?:\s+\d+)?|Core[™™]?\s+i\d+[- ]\d+\w*|Xeon[®®]?(?:\s+\w+)*)/i) |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (18)
packages/ui/components/Layouts/Home/services.tsx (1)
74-81: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win
whitespace-nowrapon the service title risks overflow for long/localized names.
service.namecomes from translations (t("servicesHome...")pattern elsewhere), and the title sits in ajustify-betweenflex row next to ashrink-0price badge. Withwhitespace-nowrapand nomin-w-0/flex-shrinkon theh3, a longer translated string could force the row wider than the card, causing the price badge to be pushed out or the title to visually clip (Card hasoverflow-hiddenso it will clip rather than reflow).💡 Proposed fix
- <h3 className="text-xl font-bold whitespace-nowrap">{service.name}</h3> + <h3 className="text-xl font-bold truncate min-w-0">{service.name}</h3>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/Home/services.tsx` around lines 74 - 81, The service title in Home/services.tsx is forced onto one line in the header row, which can overflow or clip longer translated names next to the price badge. Update the title area around the service.name render to allow wrapping or shrinking safely by removing the no-wrap behavior and adding appropriate flex handling (for example, letting the h3 shrink within the justify-between row) so long localized names don’t push the badge out of the card.packages/kb/components/kb-sidebar.tsx (1)
46-46: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winOpen state won't re-sync on client-side navigation.
useState(() => isCategoryActive)runs its initializer only on mount. Since this is a client component driven byusePathname(), navigating between KB pages doesn't remountSidebarNode, so a category that becomes active after navigation stays collapsed even though its styling updates. Consider syncing open state when the node becomes active.♻️ Possible approach
- const [isOpen, setIsOpen] = useState(() => isCategoryActive) + const [isOpen, setIsOpen] = useState(() => isCategoryActive) + + useEffect(() => { + if (isCategoryActive) setIsOpen(true) + }, [isCategoryActive])Requires adding
useEffectto thereactimport.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/kb/components/kb-sidebar.tsx` at line 46, The open state in SidebarNode is only initialized once from isCategoryActive, so it won’t update when usePathname() changes during client-side navigation. Update SidebarNode to sync isOpen whenever isCategoryActive becomes true, using a useEffect tied to that value, and make sure useEffect is imported from react alongside useState.packages/kb/lib/kb.ts (1)
284-298: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value
getAllArticlesre-walks the whole tree on every recursion.
getCategories(parentPath)already returns each category with its fully populatedsubcategories(recursively). Recursing again viagetAllArticles(cat.path)re-invokesgetCategoriesfor each subtree, rebuilding trees that were just computed — redundant filesystem reads that grow with depth. You can traverse the already-materializedcat.subcategoriesinstead, or flatten from a singlegetCategories()call.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/kb/lib/kb.ts` around lines 284 - 298, `getAllArticles` is redundantly re-reading the category tree on every recursive call by calling `getCategories(cat.path)` again. Update `getAllArticles` to traverse the already-populated `subcategories` returned by `getCategories` (or flatten from a single top-level call) instead of recursing back into `getAllArticles(cat.path)`, and keep using `getArticlesByCategory` for each category path to collect articles.packages/core/lib/bytepay.ts (1)
328-350: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate plan-resolution logic; fallback may pick a non-recurring plan.
getGbpPriceandgetPricesMapboth re-implement the same "recurring monthly → any recurring →plans[0]" fallback chain. Two issues:
- Duplication: a future change to the fallback rule (e.g. preferring annual over one-time) needs to be kept in sync across both functions.
- The final
plans[0]fallback doesn't checktype, so a product whose only plan is"one-time"or"free"will have that price surfaced as the "monthly" price for the plan card.Consider extracting a shared
resolveMonthlyPlan(product)helper used by both functions, and reconsidering whether non-recurring plans should ever be used as a price source.♻️ Proposed refactor
+function resolveMonthlyPlan(product: BillingProduct): BillingPlan | undefined { + return ( + product.plans.find((p) => p.type === "recurring" && p.billingPeriod === 1 && p.billingUnit === "month") + ?? product.plans.find((p) => p.type === "recurring") + ?? product.plans[0] + ) +} + export function getGbpPrice(product: BillingProduct): number { - const plan = - product.plans.find((p) => p.type === "recurring" && p.billingPeriod === 1 && p.billingUnit === "month") - ?? product.plans.find((p) => p.type === "recurring") - ?? product.plans[0] + const plan = resolveMonthlyPlan(product) if (!plan) return 0 return plan.prices.find((p) => p.currencyCode === "GBP")?.price ?? 0 } export function getPricesMap(product: BillingProduct): Record<string, number> { - const plan = - product.plans.find((p) => p.type === "recurring" && p.billingPeriod === 1 && p.billingUnit === "month") - ?? product.plans.find((p) => p.type === "recurring") - ?? product.plans[0] + const plan = resolveMonthlyPlan(product) if (!plan) return {} ... }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/bytepay.ts` around lines 328 - 350, `getGbpPrice` and `getPricesMap` duplicate the same plan selection logic, and both can incorrectly fall back to a non-recurring plan via `plans[0]`. Extract the shared recurring-plan resolution into a helper such as `resolveMonthlyPlan(product)` and have both functions use it. While doing so, make sure the fallback chain only returns a recurring plan (or otherwise explicitly handles the non-recurring case) so one-time/free plans are not treated as monthly pricing.packages/core/lib/spec-parser.ts (2)
151-167: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
seriesis returned unvalidated and later force-cast into a strict union.
parseProductNamereturnsseriesasparts[1] ?? undefinedwith no check against known series codes.getVpsPlansinbilling-service.tsthen doesseries: series as VpsPlanSpec["series"], so any unexpected product-name segment (typo, new naming convention, etc.) is silently accepted as if it were a valid series, defeating the purpose of the union type.Consider validating against the known series list here and returning
undefined(with a warning) for unrecognized values, mirroring thewarnDroppedProductpattern already used elsewhere in this stack.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/spec-parser.ts` around lines 151 - 167, `parseProductName` currently returns `series` from `parts[1]` without validation, which lets invalid values flow into `getVpsPlans` where they are force-cast to `VpsPlanSpec["series"]`. Update `parseProductName` to validate the parsed series against the known series list, and return `undefined` for unrecognized values while emitting a warning similar to `warnDroppedProduct`. Keep the change localized to `parseProductName` so downstream callers like `getVpsPlans` no longer need to rely on unsafe casts.
99-104: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winUplink silently defaults to a fabricated "1 Gbps" when not mentioned in the description.
When no Gbps figure is found,
uplinkis set to{ amount: 1, unit: "Gbps" }rather than leftundefined. This presents an invented spec as if it came from the billing panel; if the true port speed differs (or the field is legitimately not provided), customers see incorrect information.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/spec-parser.ts` around lines 99 - 104, The uplink parsing logic in the spec-parser file is providing a fabricated default value of 1 Gbps when no uplink speed is found in the text, rather than returning undefined. Instead of defaulting uplinkMatch to the object { amount: 1, unit: "Gbps" } when no match is found, change the ternary operator to set the uplink variable to undefined when uplinkMatch is falsy, so that missing uplink information is represented as undefined rather than as an invented specification.app/api/billing-debug/route.ts (1)
24-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDebug price computation doesn't match production fallback logic.
gbpMonthlyhere only looks for an exact recurring/monthly plan, whilegetGbpPriceinpackages/core/lib/bytepay.tsadditionally falls back to "any recurring plan" thenplans[0]. A product could showgbpMonthly: nullin this debug endpoint while still rendering a (fallback) price on the live page, undermining the tool's purpose of surfacing pricing/parsing gaps.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/api/billing-debug/route.ts` around lines 24 - 27, Align the `gbpMonthly` debug computation with the fallback behavior used by `getGbpPrice` in `packages/core/lib/bytepay.ts`: first try the exact recurring monthly GBP price, then fall back to any recurring plan, and finally to `plans[0]` if needed. Update the `route.ts` logic so this debug endpoint mirrors the live price selection path and does not return null when production would still surface a fallback price.app/vps/page.tsx (1)
11-17: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider partial degradation instead of all-or-nothing fetch.
Promise.allmeans a failure fetching eithershared-cpuordedicated-cpuplans rejects the whole page render (no shared plans shown even if only dedicated-cpu failed, or vice versa).Promise.allSettledwith per-category fallback to[]would let the page still render whichever category succeeded.♻️ Proposed fix
export default async function VpsPage() { - const [sharedPlans, dedicatedPlans] = await Promise.all([ - getVpsPlans("shared-cpu"), - getVpsPlans("dedicated-cpu"), - ]) - return <VpsHub plans={[...sharedPlans, ...dedicatedPlans]} /> + const results = await Promise.allSettled([ + getVpsPlans("shared-cpu"), + getVpsPlans("dedicated-cpu"), + ]) + const plans = results.flatMap((r) => (r.status === "fulfilled" ? r.value : [])) + return <VpsHub plans={plans} /> }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/vps/page.tsx` around lines 11 - 17, The VpsPage data fetch is all-or-nothing because Promise.all in VpsPage rejects the entire render if either getVpsPlans("shared-cpu") or getVpsPlans("dedicated-cpu") fails. Update VpsPage to tolerate partial failures by using Promise.allSettled or equivalent per-call handling, then fall back to an empty array for whichever category fails while still passing the successful plans into VpsHub.packages/ui/components/Layouts/VPS/vps-hub.tsx (1)
62-74: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueWidened
Seriestype loses compile-time key safety.
Serieswas narrowed tokeyof typeof SERIES_META, now it's plainstring, so anyplan.seriesvalue can no longer be statically checked againstSERIES_META. Runtime fallback (meta ? ... : s) prevents a crash but a mistyped series string will silently render its raw key without failing the build.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/VPS/vps-hub.tsx` around lines 62 - 74, `Series` was widened to plain `string`, which removes compile-time validation against `SERIES_META` and allows mistyped series keys to slip through. Update the `Series` type in `vps-hub.tsx` to be derived from `SERIES_META` (like the existing `Lineup` pattern) and make sure any `plan.series` usage and related helpers continue to reference the strongly typed `SERIES_META` keys so invalid series values are caught at build time.packages/core/lib/status.ts (3)
44-68: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winConsider guarding this module against client-side import.
The docstring at Line 2 states
STATUS_TOKEN is never sent to the browser, which holds only because this module is always invoked server-side. Adding theserver-onlypackage import would enforce that boundary at build time instead of relying on convention, preventing an accidental"use client"import (e.g. via a future refactor) from silently breaking this guarantee.🔒 Proposed fix
+import "server-only" + /** * Server-side only — STATUS_TOKEN is never sent to the browser. *🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/status.ts` around lines 44 - 68, This module depends on server-only behavior, so add a server-only import at the top of the status module to enforce that it cannot be bundled into client code. Update the status-related entry point around fetchStatusJson so the browser cannot accidentally import it through a future refactor, preserving the STATUS_TOKEN handling guarantee at build time.
77-97: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winUnvalidated
ascasts on external API response.
fetchStatusSnapshotcasts every field from the untrusted upstream JSON response (m.id as number,m.status as MonitorStatus,data.overall_status as MonitorStatus, etc.) without runtime validation. If status.nodebyte.host changes its schema or returns an unexpected value (e.g. a new status enum member, ornullwhere a string is expected), these casts won't catch it — the bad data will propagate silently intoStatusApiResponseand then intoSTATUS_DOT/STATUS_LABEL_KEYlookups instatus-badge.tsx, which only handle known keys.Consider adding a lightweight runtime validator (e.g. a Zod schema, or explicit
typeof/enum checks) so malformed upstream payloads are caught in the existingcatchblock and degrade gracefully tonullinstead of shipping incorrect types downstream.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/status.ts` around lines 77 - 97, The `fetchStatusSnapshot` parsing in `status.ts` is trusting unvalidated upstream JSON with `as` casts for fields like `m.id`, `m.status`, and `data.overall_status`, so malformed or new API values can leak into downstream consumers. Add runtime validation in `fetchStatusSnapshot` (for example with a schema or explicit type/enum checks) before building `monitors` and the returned object. Ensure unexpected or invalid fields are rejected and handled by the existing `catch` path so the function returns a safe fallback instead of propagating bad types.
112-123: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winSpread-based
Math.min/Math.maxrisks a stack overflow on large heartbeat arrays.
Math.min(...samples)/Math.max(...samples)push every element onto the call stack as an argument; large arrays (~100k+ elements in V8) throwRangeError: Maximum call stack size exceeded. Sinceheartbeatsoriginates from an external, unvalidated API response (Line 86), an unexpectedly large payload would crash this computation — and sincecomputeLatencyStatsis called directly (unguarded) fromapp/api/status/route.ts, that would surface as a route failure rather than a graceful degradation.A reduce-based implementation avoids the risk entirely at negligible cost.
🛡️ Proposed fix
if (samples.length === 0) return null - const fast = Math.min(...samples) - const slow = Math.max(...samples) + const fast = samples.reduce((min, ms) => (ms < min ? ms : min), samples[0]) + const slow = samples.reduce((max, ms) => (ms > max ? ms : max), samples[0]) const avg = Math.round(samples.reduce((sum, ms) => sum + ms, 0) / samples.length) return { fast, avg, slow }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/status.ts` around lines 112 - 123, computeLatencyStats currently uses spread with Math.min/Math.max over monitor.heartbeats, which can overflow the call stack on large inputs. Update computeLatencyStats to derive fast and slow with a reduce/loop-based approach over the filtered samples, keeping the same return shape and using the existing samples logic.packages/ui/components/Static/status-badge.tsx (1)
8-24: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winType these maps against
MonitorStatusfor exhaustiveness checking.
STATUS_DOTandSTATUS_LABEL_KEYare declared asRecord<string, string>. Typing them asRecord<MonitorStatus, string>(imported frompackages/core/lib/status) would make TypeScript flag any future addition/removal of aMonitorStatusmember as a compile error here, instead of silently falling back toundefinedstyling/labels at runtime.♻️ Proposed fix
+import type { MonitorStatus } from "`@/packages/core/lib/status`" -const STATUS_DOT: Record<string, string> = { +const STATUS_DOT: Record<MonitorStatus, string> = { up: "bg-green-400 animate-pulse", degraded: "bg-amber-400", maintenance: "bg-amber-400", paused: "bg-amber-400", down: "bg-red-400", unknown: "bg-muted-foreground", } -const STATUS_LABEL_KEY: Record<string, string> = { +const STATUS_LABEL_KEY: Record<MonitorStatus, string> = { up: "operational", degraded: "degraded", maintenance: "maintenance", paused: "maintenance", down: "down", unknown: "unavailable", }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Static/status-badge.tsx` around lines 8 - 24, Type the STATUS_DOT and STATUS_LABEL_KEY maps in status-badge.tsx against MonitorStatus instead of plain string keys so the compiler enforces exhaustiveness. Import MonitorStatus from packages/core/lib/status, update both Record types to use it, and keep the existing status-to-style/label mappings so any future MonitorStatus change surfaces as a compile error in these constants.packages/core/hooks/use-node-status.ts (1)
14-35: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSolid cleanup pattern; consider surfacing fetch errors.
The
cancelledflag correctly prevents state updates after unmount, and the interval is properly cleared. One gap:.catch(() => {})(Line 26) silently swallows fetch/parse failures with no way for consumers to distinguish "still loading," "fetched successfully," and "fetch failed" — all render asavailable: false. Exposing anerror/loadingfield would letStatusBadgeshow a more accurate degraded state.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/hooks/use-node-status.ts` around lines 14 - 35, The useNodeStatus hook currently swallows request and parsing failures in the load flow, so consumers cannot tell loading, success, and error states apart. Update use-node-status’s useEffect/load logic to capture fetch failures instead of ignoring them, and extend the hook’s returned state with an explicit error and/or loading field alongside available, monitors, and overallStatus. Then adjust StatusBadge to read that new state so it can display a degraded or error status when /api/status fails..env.example (1)
9-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd trailing newline at end of file.
dotenv-linter flags a missing blank line at the end of the file after the new
STATUS_TOKENentry.🧹 Proposed fix
STATUS_API_URL="https://status.nodebyte.host" -STATUS_TOKEN="" +STATUS_TOKEN="" +🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.env.example around lines 9 - 14, The .env.example file is missing the required trailing blank line at the end of the file after the STATUS_TOKEN entry, which dotenv-linter flags. Update the file so the final line is followed by a newline/empty line, keeping the existing DEEPL_API_KEY, STATUS_API_URL, and STATUS_TOKEN entries unchanged.Source: Linters/SAST tools
packages/ui/components/Layouts/Nodes/nodes-client.tsx (3)
26-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueType imported from an API route module into a UI component.
StatusApiMonitoris defined inapp/api/status/route.tsand imported into this client component. It'simport type, so it's erased at build time and safe functionally, but colocating the shared contract type in a route file couples the UI to route internals. Consider hoistingStatusApiMonitor/StatusApiResponseintopackages/core/lib/status.ts(or a shared types module) for cleaner module boundaries.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx` around lines 26 - 28, The UI client component is importing the shared status contract type from an API route module, which couples `nodes-client.tsx` to route internals. Move `StatusApiMonitor` (and related `StatusApiResponse` if needed) into a shared types module such as `packages/core/lib/status.ts`, then update `nodes-client.tsx` and the status route to import from that shared location instead of `app/api/status/route`.
121-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate style literals instead of reusing
LIVE_STATE_STYLES.The fallback branch re-declares the "Maintenance"/"Online" style objects verbatim instead of referencing
LIVE_STATE_STYLES.maintenance/LIVE_STATE_STYLES.up, risking drift if one copy is updated without the other.♻️ Suggested dedup
function resolveNodeState(node: ExtendedNode, live: StatusApiMonitor | null) { if (live && live.status in LIVE_STATE_STYLES) { return LIVE_STATE_STYLES[live.status] } - return node.isMaintenanceMode - ? { label: "Maintenance", dot: "bg-amber-400", border: "hover:border-amber-500/30 hover:shadow-xl hover:shadow-amber-500/5", badge: "border-amber-500/30 text-amber-400 bg-amber-500/5" } - : { label: "Online", dot: "bg-green-400 animate-pulse", border: "hover:border-green-500/30 hover:shadow-xl hover:shadow-green-500/5", badge: "border-green-500/30 text-green-400 bg-green-500/5" } + return node.isMaintenanceMode ? LIVE_STATE_STYLES.maintenance : LIVE_STATE_STYLES.up }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx` around lines 121 - 128, The fallback in resolveNodeState duplicates the Maintenance and Online style objects instead of reusing the shared LIVE_STATE_STYLES entries. Update the node.isMaintenanceMode branch to return LIVE_STATE_STYLES.maintenance and the online branch to return LIVE_STATE_STYLES.up so resolveNodeState stays consistent and avoids drift from duplicated literals.
156-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winColor derived via fragile string matching on class names/labels.
state.dot.includes("green"/"red")andstate.label === "Online"/"Offline"re-derive a color from strings meant for CSS classes. It works today since all 4 states are covered, but it's brittle for future states and duplicates logic already encoded inLIVE_STATE_STYLES.♻️ Suggested structured color field
-const LIVE_STATE_STYLES: Record<string, { label: string; dot: string; border: string; badge: string }> = { - up: { label: "Online", dot: "bg-green-400 animate-pulse", ... }, +const LIVE_STATE_STYLES: Record<string, { label: string; color: "green" | "amber" | "red"; dot: string; border: string; badge: string }> = { + up: { label: "Online", color: "green", dot: "bg-green-400 animate-pulse", ... }, ... }Then use
state.color === "green" ? "bg-green-500" : ...instead of string matching ondot/label.Also applies to: 161-164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx` at line 156, The live state indicator is re-deriving colors from display strings via state.dot.includes(...) and state.label checks, which is brittle and duplicates the intent already defined in LIVE_STATE_STYLES. Update the nodes-client.tsx rendering logic to use a structured color field on the state object (for example state.color) as the single source of truth for both the top bar and other affected UI pieces, and replace the string matching in the live state markup with direct color-based branching.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/api/status/route.ts`:
- Line 4: The fallback response in the status route is missing the same caching
behavior as the success path, so make the handler apply the same Cache-Control
header when it returns the 200 fallback. Update the status route handler logic
in the route’s response construction so both the normal and fallback responses
share the same cache window, using the existing status route response handling
as the reference point.
In `@app/games/hytale/page.tsx`:
- Around line 27-33: The Hytale plan mapping in the page render can crash when a
live billing slug is not present in HYTALE_PLAN_DISPLAY, because display may be
undefined before accessing display.name and display.description. Update the
plans transform in the page component to guard the lookup in the
getGamePlans("hytale") map, either by skipping plans with no matching display
entry or by providing a safe fallback object, and keep the logic centered around
the HYTALE_PLAN_DISPLAY and plans mapping in app/games/hytale/page.tsx.
In `@app/kb/`[...path]/page.tsx:
- Around line 111-115: The KB catch-all route in KBDynamicPage currently
forwards raw path segments from params into resolvePath and downstream
article/category lookup, so add validation before resolvePath to reject any
segments containing path traversal or separator characters such as .., /, or \.
If this page should only serve known routes, alternatively set dynamicParams =
false; otherwise ensure only safe, normalized segments can reach getArticle and
getArticlesByCategory.
In `@packages/core/constants/product-overrides.ts`:
- Around line 1-9: The documented SERIES_LOCATION mapping is missing, and
getVpsPlans currently hardcodes VPS location to undefined, so the datacenter
city is never populated. Add the SERIES_LOCATION export in product-overrides.ts
and use it in billing-service.ts within getVpsPlans to derive location from the
plan’s series code instead of assigning undefined. Make sure the new mapping is
referenced consistently by the existing series/location doc comments.
In `@packages/core/lib/spec-parser.ts`:
- Around line 106-120: The bandwidth parsing in spec-parser.ts only matches TB
values, so GB allowances are misclassified as null/unlimited. Update the
bandwidth extraction logic in the spec-parser function to recognize GB (and MB
if supported by ParsedSpecs["bandwidth"]) alongside TB, and ensure the unit
captured from the regex is preserved when constructing the
ParsedSpecs["bandwidth"] result. Keep the unlimitedBw check separate so only
true unmetered descriptions map to null.
- Around line 131-138: The Intel detection in spec-parser.ts is overriding
earlier AMD/ARM matches because the intelMatch branch in the parsing logic does
not check whether ryzenMatch or ampereMatch was already found. Update the CPU
detection flow in the relevant spec-parser logic so the Intel assignment only
runs when no AMD/ARM match has been established, matching the existing guards
used in the fallback branches for ryzenMatch and ampereMatch. Keep the
cpuModel/hardware precedence consistent within the same detection block.
In `@packages/core/products/billing-service.ts`:
- Around line 73-86: The JSDoc for getDedicatedPlans overstates the validation:
the implementation only filters out products missing parsed.ramGB, while cores
and storageGB may still be undefined and are returned as optional fields in
DedicatedPlanSpec. Update the comment to describe the actual ramGB-only
requirement, or, if cores/storage should be required, add matching checks in
getDedicatedPlans and keep warnDroppedProduct in sync with the rejected fields.
In `@packages/core/types/servers/dedicated.ts`:
- Line 20: DedicatedPlanSpec.hardware is missing the "arm" value even though
spec parsing and billing already pass it through at runtime. Update the
DedicatedPlanSpec type in the dedicated server type definition to include "arm"
alongside the existing hardware options, and make sure any related unions or
assertions that reference hardware stay aligned with the expanded set.
In `@packages/kb/components/kb-sidebar.tsx`:
- Around line 43-44: The active-state check in kb-sidebar’s category matching is
too broad because pathname.startsWith(categoryPrefix) can mark sibling
categories like game active when visiting games. Update the logic in
kb-sidebar.tsx around categoryPrefix/isCategoryActive to require a segment
boundary after the category path (while still allowing ancestor paths to remain
active), so only the exact category or its descendants match.
In `@packages/kb/lib/kb.ts`:
- Around line 168-179: The category sorting in kb.ts is incorrectly treating
order 0 as missing because the comparator uses a falsy fallback. Update the sort
logic in the return from the category-building function to use a numeric-safe
fallback that preserves legitimate zero values, and keep the change localized to
the categories.sort comparator so explicit order values from meta are respected.
- Around line 244-248: The articles sorting logic in kb.ts still treats order 0
as 999 in the comparator, so items with a valid zero order are placed last and
break prev/next navigation. Update the sorting in the article list builder that
returns articles.sort(...) to use a numeric comparison based on the actual order
values without falling back on truthiness, so order: 0 is preserved consistently
with the existing data.order ?? 999 assignment.
In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx`:
- Around line 307-317: The aggregate “In Maintenance” stat is using a simple
onlineCount subtraction in NodesClient, which incorrectly buckets degraded/down
nodes as maintenance. Update the hero stat calculation to derive counts from the
same node-state logic used by each NodeCard, ideally via resolveNodeState, so
only true maintenance nodes are counted there and degraded/offline nodes remain
separate. Keep the aggregation consistent wherever maintenanceCount is computed
so the summary matches the per-node badges.
In `@packages/ui/components/Static/footer.tsx`:
- Around line 122-125: The footer Services entry for
`t("footer.services.systemStatus")` is hardcoded to the looking-glass URL
instead of the actual status page. Update the link in `Footer` to use the
existing `LINKS.status` constant, matching the `StatusBadge` behavior in this
file and avoiding the hardcoded `https://lg.nodebyte.host` value.
---
Nitpick comments:
In @.env.example:
- Around line 9-14: The .env.example file is missing the required trailing blank
line at the end of the file after the STATUS_TOKEN entry, which dotenv-linter
flags. Update the file so the final line is followed by a newline/empty line,
keeping the existing DEEPL_API_KEY, STATUS_API_URL, and STATUS_TOKEN entries
unchanged.
In `@app/api/billing-debug/route.ts`:
- Around line 24-27: Align the `gbpMonthly` debug computation with the fallback
behavior used by `getGbpPrice` in `packages/core/lib/bytepay.ts`: first try the
exact recurring monthly GBP price, then fall back to any recurring plan, and
finally to `plans[0]` if needed. Update the `route.ts` logic so this debug
endpoint mirrors the live price selection path and does not return null when
production would still surface a fallback price.
In `@app/vps/page.tsx`:
- Around line 11-17: The VpsPage data fetch is all-or-nothing because
Promise.all in VpsPage rejects the entire render if either
getVpsPlans("shared-cpu") or getVpsPlans("dedicated-cpu") fails. Update VpsPage
to tolerate partial failures by using Promise.allSettled or equivalent per-call
handling, then fall back to an empty array for whichever category fails while
still passing the successful plans into VpsHub.
In `@packages/core/hooks/use-node-status.ts`:
- Around line 14-35: The useNodeStatus hook currently swallows request and
parsing failures in the load flow, so consumers cannot tell loading, success,
and error states apart. Update use-node-status’s useEffect/load logic to capture
fetch failures instead of ignoring them, and extend the hook’s returned state
with an explicit error and/or loading field alongside available, monitors, and
overallStatus. Then adjust StatusBadge to read that new state so it can display
a degraded or error status when /api/status fails.
In `@packages/core/lib/bytepay.ts`:
- Around line 328-350: `getGbpPrice` and `getPricesMap` duplicate the same plan
selection logic, and both can incorrectly fall back to a non-recurring plan via
`plans[0]`. Extract the shared recurring-plan resolution into a helper such as
`resolveMonthlyPlan(product)` and have both functions use it. While doing so,
make sure the fallback chain only returns a recurring plan (or otherwise
explicitly handles the non-recurring case) so one-time/free plans are not
treated as monthly pricing.
In `@packages/core/lib/spec-parser.ts`:
- Around line 151-167: `parseProductName` currently returns `series` from
`parts[1]` without validation, which lets invalid values flow into `getVpsPlans`
where they are force-cast to `VpsPlanSpec["series"]`. Update `parseProductName`
to validate the parsed series against the known series list, and return
`undefined` for unrecognized values while emitting a warning similar to
`warnDroppedProduct`. Keep the change localized to `parseProductName` so
downstream callers like `getVpsPlans` no longer need to rely on unsafe casts.
- Around line 99-104: The uplink parsing logic in the spec-parser file is
providing a fabricated default value of 1 Gbps when no uplink speed is found in
the text, rather than returning undefined. Instead of defaulting uplinkMatch to
the object { amount: 1, unit: "Gbps" } when no match is found, change the
ternary operator to set the uplink variable to undefined when uplinkMatch is
falsy, so that missing uplink information is represented as undefined rather
than as an invented specification.
In `@packages/core/lib/status.ts`:
- Around line 44-68: This module depends on server-only behavior, so add a
server-only import at the top of the status module to enforce that it cannot be
bundled into client code. Update the status-related entry point around
fetchStatusJson so the browser cannot accidentally import it through a future
refactor, preserving the STATUS_TOKEN handling guarantee at build time.
- Around line 77-97: The `fetchStatusSnapshot` parsing in `status.ts` is
trusting unvalidated upstream JSON with `as` casts for fields like `m.id`,
`m.status`, and `data.overall_status`, so malformed or new API values can leak
into downstream consumers. Add runtime validation in `fetchStatusSnapshot` (for
example with a schema or explicit type/enum checks) before building `monitors`
and the returned object. Ensure unexpected or invalid fields are rejected and
handled by the existing `catch` path so the function returns a safe fallback
instead of propagating bad types.
- Around line 112-123: computeLatencyStats currently uses spread with
Math.min/Math.max over monitor.heartbeats, which can overflow the call stack on
large inputs. Update computeLatencyStats to derive fast and slow with a
reduce/loop-based approach over the filtered samples, keeping the same return
shape and using the existing samples logic.
In `@packages/kb/components/kb-sidebar.tsx`:
- Line 46: The open state in SidebarNode is only initialized once from
isCategoryActive, so it won’t update when usePathname() changes during
client-side navigation. Update SidebarNode to sync isOpen whenever
isCategoryActive becomes true, using a useEffect tied to that value, and make
sure useEffect is imported from react alongside useState.
In `@packages/kb/lib/kb.ts`:
- Around line 284-298: `getAllArticles` is redundantly re-reading the category
tree on every recursive call by calling `getCategories(cat.path)` again. Update
`getAllArticles` to traverse the already-populated `subcategories` returned by
`getCategories` (or flatten from a single top-level call) instead of recursing
back into `getAllArticles(cat.path)`, and keep using `getArticlesByCategory` for
each category path to collect articles.
In `@packages/ui/components/Layouts/Home/services.tsx`:
- Around line 74-81: The service title in Home/services.tsx is forced onto one
line in the header row, which can overflow or clip longer translated names next
to the price badge. Update the title area around the service.name render to
allow wrapping or shrinking safely by removing the no-wrap behavior and adding
appropriate flex handling (for example, letting the h3 shrink within the
justify-between row) so long localized names don’t push the badge out of the
card.
In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx`:
- Around line 26-28: The UI client component is importing the shared status
contract type from an API route module, which couples `nodes-client.tsx` to
route internals. Move `StatusApiMonitor` (and related `StatusApiResponse` if
needed) into a shared types module such as `packages/core/lib/status.ts`, then
update `nodes-client.tsx` and the status route to import from that shared
location instead of `app/api/status/route`.
- Around line 121-128: The fallback in resolveNodeState duplicates the
Maintenance and Online style objects instead of reusing the shared
LIVE_STATE_STYLES entries. Update the node.isMaintenanceMode branch to return
LIVE_STATE_STYLES.maintenance and the online branch to return
LIVE_STATE_STYLES.up so resolveNodeState stays consistent and avoids drift from
duplicated literals.
- Line 156: The live state indicator is re-deriving colors from display strings
via state.dot.includes(...) and state.label checks, which is brittle and
duplicates the intent already defined in LIVE_STATE_STYLES. Update the
nodes-client.tsx rendering logic to use a structured color field on the state
object (for example state.color) as the single source of truth for both the top
bar and other affected UI pieces, and replace the string matching in the live
state markup with direct color-based branching.
In `@packages/ui/components/Layouts/VPS/vps-hub.tsx`:
- Around line 62-74: `Series` was widened to plain `string`, which removes
compile-time validation against `SERIES_META` and allows mistyped series keys to
slip through. Update the `Series` type in `vps-hub.tsx` to be derived from
`SERIES_META` (like the existing `Lineup` pattern) and make sure any
`plan.series` usage and related helpers continue to reference the strongly typed
`SERIES_META` keys so invalid series values are caught at build time.
In `@packages/ui/components/Static/status-badge.tsx`:
- Around line 8-24: Type the STATUS_DOT and STATUS_LABEL_KEY maps in
status-badge.tsx against MonitorStatus instead of plain string keys so the
compiler enforces exhaustiveness. Import MonitorStatus from
packages/core/lib/status, update both Record types to use it, and keep the
existing status-to-style/label mappings so any future MonitorStatus change
surfaces as a compile error in these constants.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: e5a51e8c-e582-463b-9b0c-0e035092ded7
📒 Files selected for processing (63)
.env.exampleCHANGELOG.mdREADME.mdapp/api/billing-debug/route.tsapp/api/instatus/route.tsapp/api/status/route.tsapp/dedicated/page.tsxapp/games/hytale/page.tsxapp/games/minecraft/page.tsxapp/games/rust/page.tsxapp/kb/[...path]/page.tsxapp/kb/[category]/[article]/page.tsxapp/kb/[category]/page.tsxapp/kb/page.tsxapp/vps/page.tsxpackages/core/constants/game/hytale.tspackages/core/constants/links.tspackages/core/constants/product-overrides.tspackages/core/constants/services.tspackages/core/constants/status-mapping.tspackages/core/hooks/use-node-status.tspackages/core/lib/bytepay.tspackages/core/lib/spec-parser.tspackages/core/lib/status.tspackages/core/products/billing-service.tspackages/core/types/servers/dedicated.tspackages/core/types/servers/game.tspackages/core/types/servers/vps.tspackages/kb/components/kb-article-card.tsxpackages/kb/components/kb-article.tsxpackages/kb/components/kb-category-card.tsxpackages/kb/components/kb-search.tsxpackages/kb/components/kb-sidebar.tsxpackages/kb/content/billing/_meta.jsonpackages/kb/content/games/hytale/operating-yourself.mdpackages/kb/content/games/hytale/setting-motd.mdpackages/kb/content/games/hytale/setting-server-name.mdpackages/kb/content/games/hytale/setting-server-password.mdpackages/kb/content/games/hytale/setting-up-hytale.mdpackages/kb/content/games/rust/_meta.jsonpackages/kb/content/games/rust/add-additional-ports.mdpackages/kb/content/games/rust/connect-to-server.mdpackages/kb/content/games/rust/enabling-rust+.mdpackages/kb/content/games/rust/getting-started.mdpackages/kb/content/games/rust/oxide-installation.mdpackages/kb/lib/kb.tspackages/ui/components/Layouts/About/about-page.tsxpackages/ui/components/Layouts/Contact/contact.tsxpackages/ui/components/Layouts/Dedicated/dedicated-hub.tsxpackages/ui/components/Layouts/Error/error-page.tsxpackages/ui/components/Layouts/Error/not-found-page.tsxpackages/ui/components/Layouts/Games/game-pricing.tsxpackages/ui/components/Layouts/Home/hero.tsxpackages/ui/components/Layouts/Home/services.tsxpackages/ui/components/Layouts/Nodes/nodes-client.tsxpackages/ui/components/Layouts/VPS/vps-hero.tsxpackages/ui/components/Layouts/VPS/vps-hub.tsxpackages/ui/components/Static/footer.tsxpackages/ui/components/Static/navigation.tsxpackages/ui/components/Static/status-badge.tsxpackages/ui/components/theme-toggle.tsxpackages/ui/components/ui/price.tsxtranslations
💤 Files with no reviewable changes (3)
- app/kb/[category]/page.tsx
- app/kb/[category]/[article]/page.tsx
- app/api/instatus/route.ts
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
🛑 Comments failed to post (13)
app/api/status/route.ts (1)
4-4: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Next.js 16 route handler revalidate export without dynamic force-static caching behavior💡 Result:
In Next.js 16, exporting
revalidatefrom a Route Handler dictates the cache duration for that route segment [1][2]. Settingexport const revalidate = <number>tells Next.js to cache the response and revalidate it after the specified number of seconds [2]. Crucially, you can userevalidatewithout explicitly settingexport const dynamic = 'force-static'. Under this configuration, the Route Handler will behave as follows: 1. Static Caching: By setting a positiverevalidatevalue, you are opting the Route Handler into static rendering/caching behavior [2]. Even withoutforce-static, the presence of a revalidation interval typically informs Next.js that the route is eligible for static generation if no dynamic APIs (likecookies()orheaders()) are accessed [3][2]. 2. Interaction withdynamic: If you do not setdynamic, Next.js defaults to'auto'[2]. Whenrevalidateis present, it effectively influences the heuristic used by the compiler to determine if the route should be static [2]. 3. Stale-While-Revalidate (SWR): When the cache expires after therevalidateduration, the next request will serve the stale cached content while the Route Handler re-runs in the background to refresh the cache [4][5]. Important Note for Next.js 16: If your Route Handler uses functions likerevalidateTag(), note that in Next.js 16, this function now requires a second argument (acacheLifeprofile, e.g.,'max') to enable SWR behavior [4][6]. The previous behavior of revalidating immediately (or triggering a cache miss) has been deprecated and now often requires explicit configuration (such as{ expire: 0 }for immediate expiration) [5]. Summary of Behavior: - Withrevalidateset: The route is cached. It will be served stale and updated in the background based on the specified interval [4][2]. - Withoutforce-static: The route remains "static" as long as it doesn't encounter dynamic functions at runtime [3][2]. If it does use dynamic functions, therevalidateexport may be ignored or conflict, leading to dynamic execution [2][7]. It is best practice to keep Route Handlers clean if you intend for them to be cached statically [3].Citations:
- 1: https://github.com/vercel/next.js/blob/v16.2.3/docs/01-app/03-api-reference/03-file-conventions/route.mdx
- 2: https://www.reflowhub.dev/en/docs/nextjs/app-api-reference/app-api-reference-file-conventions/app-api-reference-file-conventions-route-segment-config
- 3: https://nextjs.org/docs/app/getting-started/route-handlers
- 4: https://nextjs.org/blog/next-16
- 5: https://en.nextjs.im/docs/app/api-reference/functions/revalidatetag/
- 6: https://mudassirworks.hashnode.dev/next-js-16-caching-demystified-what-changed-what-broke-and-how-to-fix-it
- 7: https://dev.to/jtorchia/nextjs-app-router-caching-revalidate-dynamic-and-no-store-without-the-folklore-2db3
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== file list ==\n' git ls-files app/api/status/route.ts app/api/status -z | tr '\0' '\n' printf '\n== route.ts lines ==\n' cat -n app/api/status/route.ts printf '\n== surrounding references to fetchStatusSnapshot ==\n' rg -n "fetchStatusSnapshot|Cache-Control|revalidate|dynamic =" app/api/status -SRepository: NodeByteHosting/website
Length of output: 2458
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== route.ts outline ==\n' ast-grep outline app/api/status/route.ts --view expanded printf '\n== next config and route-handler docs references in repo ==\n' rg -n "dynamic = 'force-static'|dynamic = \"force-static\"|revalidate =|Route Handler|route handler|cache" -S . --glob '!**/node_modules/**' --glob '!**/.next/**' | head -n 200Repository: NodeByteHosting/website
Length of output: 5936
🌐 Web query:
Next.js route handler revalidate cached response docs dynamic auto force-static official💡 Result:
In Next.js, Route Handlers are not cached by default [1]. To control caching and revalidation behavior, you can use segment configuration options and on-demand revalidation functions [2]. Caching and Revalidation Options 1. Segment Configuration: You can control the caching behavior of a Route Handler by exporting specific configuration constants from your route file (e.g., route.ts) [2][3]. - export const dynamic = 'force-static': This forces the route to be prerendered and cached, effectively turning it into a static route [2][4]. - export const revalidate = n: Sets the default revalidation frequency for the route to n seconds [2]. - export const dynamic = 'auto' (default): Automatically determines whether the route should be static or dynamic based on its use of Request-time APIs like cookies or headers [2][5]. 2. On-demand Revalidation: You can invalidate cached data programmatically using functions in a Server Action or Route Handler [2]. - revalidatePath(path): Invalidates all cached data associated with a specific path [6][2]. - revalidateTag(tag): Invalidates cached data associated with a specific cache tag [2][7]. Static Generation with generateStaticParams To statically generate routes at build time (often used with force-static or dynamicParams config), you can use generateStaticParams [8][4]. This allows you to define the set of parameters for which the route should be prerendered [4]. When using dynamic segments, you must return an array (even if empty) from generateStaticParams to allow for ISR (Incremental Static Regeneration) at runtime [4]. Precedence Segment configuration options generally take precedence over individual fetch options [5]. For example, if a segment is configured with force-dynamic, all fetch requests within that segment will behave as dynamic regardless of their individual cache settings [5]. It is recommended to carefully review the Next.js precedence documentation when mixing segment-level and fetch-level configurations [5].
Citations:
- 1: https://nextjs.org/docs/app/getting-started/route-handlers
- 2: https://nextjs.org/docs/14/app/building-your-application/caching
- 3: https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
- 4: https://nextjs.org/docs/14/app/api-reference/functions/generate-static-params
- 5: https://juanchi.dev/en/blog/nextjs-app-router-caching-revalidate-dynamic-no-store-2
- 6: https://nextjs.org/docs/app/api-reference/functions/revalidatePath
- 7: https://nextjs.org/docs/app/api-reference/functions/revalidateTag
- 8: https://nextjs.org/docs/app/api-reference/functions/generate-static-params
🏁 Script executed:
#!/bin/bash set -euo pipefail # Inspect the file if present and route handler docs references in the repo. cat -n app/api/status/route.ts 2>/dev/null || true printf '\n--\n' rg -n "fetchStatusSnapshot|Cache-Control|export const revalidate|export const dynamic" app/api/status -S 2>/dev/null || trueRepository: NodeByteHosting/website
Length of output: 2346
🌐 Web query:
Next.js route handler segment config revalidate official docs route.ts caching behavior💡 Result:
In Next.js, Route Handlers (route.ts) utilize Route Segment Config to control caching and revalidation behavior [1][2]. Caching Behavior: Route Handlers are not cached by default [3]. However, you can opt into caching specifically for GET methods [3]. Other HTTP methods (POST, PUT, DELETE, etc.) are not cached [3]. A known limitation is that if a route.ts file contains both a GET method and other HTTP methods, the file may not be cached, as the caching mechanism is currently method-invariant [4]. Segment Config Options: You can configure these behaviors by exporting specific variables from your route.ts file [1][5]: 1. revalidate: This option allows you to set the cache lifetime in seconds (e.g., export const revalidate = 60) [6]. 2. dynamic: You can use export const dynamic = 'force-static' to force a route to be statically rendered (cached) or 'force-dynamic' to ensure it is always executed dynamically [1][7][8]. Revalidation: Beyond static revalidation (using the revalidate config), you can programmatically revalidate cached data using functions like revalidatePath [9]. Note that in newer versions of Next.js, specific segment config options like revalidate and dynamic may be subject to changes as the framework transitions toward new caching models (e.g., the Cache Components model) [7]. Always check your specific Next.js version documentation for the most accurate implementation details [1][7].
Citations:
- 1: https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config
- 2: https://nextjs.org/docs/app/api-reference/file-conventions/route
- 3: https://nextjs.org/docs/app/getting-started/route-handlers
- 4: vercel/next.js#77185
- 5: https://nextjs.org/docs/app/guides/caching-without-cache-components
- 6: https://nextjs.org/docs/14/app/building-your-application/routing/route-handlers
- 7: https://nextjs.org/docs/15/app/api-reference/file-conventions/route-segment-config
- 8: vercel/next.js#62725
- 9: https://nextjs.org/docs/app/api-reference/functions/revalidatePath
Cache the fallback response too
app/api/status/route.ts:26-30returns200without theCache-Controlheader used on the success path, so outage responses can bypass the same cache window and keep hammering the origin.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/api/status/route.ts` at line 4, The fallback response in the status route is missing the same caching behavior as the success path, so make the handler apply the same Cache-Control header when it returns the 200 fallback. Update the status route handler logic in the route’s response construction so both the normal and fallback responses share the same cache window, using the existing status route response handling as the reference point.app/games/hytale/page.tsx (1)
27-33: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== app/games/hytale/page.tsx ==\n' if [ -f app/games/hytale/page.tsx ]; then cat -n app/games/hytale/page.tsx | sed -n '1,220p' else echo "Missing app/games/hytale/page.tsx" fi printf '\n== Search for HYTALE_PLAN_DISPLAY and getGamePlans ==\n' rg -n "HYTALE_PLAN_DISPLAY|getGamePlans\\(" app lib src . --glob '!node_modules' --glob '!dist' --glob '!build' || true printf '\n== Files containing GamePlanSpec or plan.id ==\n' rg -n "GamePlanSpec|plan\\.id|product\\.slug|GamePlan" app lib src . --glob '!node_modules' --glob '!dist' --glob '!build' || trueRepository: NodeByteHosting/website
Length of output: 12537
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== packages/core/constants/game/hytale.ts ==\n' cat -n packages/core/constants/game/hytale.ts | sed -n '1,220p' printf '\n== packages/core/products/billing-service.ts ==\n' cat -n packages/core/products/billing-service.ts | sed -n '1,220p' printf '\n== packages/core/types/servers/game.ts ==\n' cat -n packages/core/types/servers/game.ts | sed -n '1,220p'Repository: NodeByteHosting/website
Length of output: 15579
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== Search for Hytale slug generation and product seeding ==\n' rg -n '"hytale-(starter|standard|performance)"|fromGamePlan|planId: plan\.id|slug:|product\.slug' packages app --glob '!node_modules' --glob '!dist' --glob '!build' || true printf '\n== packages/core/products/service.ts ==\n' cat -n packages/core/products/service.ts | sed -n '1,220p' printf '\n== app/api/billing-debug/route.ts ==\n' cat -n app/api/billing-debug/route.ts | sed -n '1,220p'Repository: NodeByteHosting/website
Length of output: 11100
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== Search for billing sync / Paymenter source-of-truth notes ==\n' rg -n "Paymenter|billing panel|source of truth|sync.*billing|seed.*billing|mirror.*billing|live billing|billing products" packages app README.md CHANGELOG.md --glob '!node_modules' --glob '!dist' --glob '!build' || true printf '\n== packages/core/lib/bytepay.ts ==\n' cat -n packages/core/lib/bytepay.ts | sed -n '1,320p'Repository: NodeByteHosting/website
Length of output: 14158
Guard the Hytale plan lookup at app/games/hytale/page.tsx:27-33
HYTALE_PLAN_DISPLAY[plan.id as keyof typeof HYTALE_PLAN_DISPLAY]can be undefined for a live billing slug that doesn't match the hardcoded keys, anddisplay.namewill throw during render. Skip unknown plans or provide a fallback display entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/games/hytale/page.tsx` around lines 27 - 33, The Hytale plan mapping in the page render can crash when a live billing slug is not present in HYTALE_PLAN_DISPLAY, because display may be undefined before accessing display.name and display.description. Update the plans transform in the page component to guard the lookup in the getGamePlans("hytale") map, either by skipping plans with no matching display entry or by providing a safe fallback object, and keep the logic centered around the HYTALE_PLAN_DISPLAY and plans mapping in app/games/hytale/page.tsx.app/kb/[...path]/page.tsx (1)
111-115: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash rg -nP 'export\s+const\s+dynamicParams' app/kbRepository: NodeByteHosting/website
Length of output: 161
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## app/kb/[...path]/page.tsx (outline)\n' ast-grep outline app/kb/'[...path]'/page.tsx --view expanded || true printf '\n## app/kb/[...path]/page.tsx (selected lines)\n' cat -n app/kb/'[...path]'/page.tsx | sed -n '1,220p' printf '\n## locate kb.ts and outline\n' fd -a 'kb.ts' . ast-grep outline app/kb/kb.ts --view expanded || true printf '\n## app/kb/kb.ts (selected lines)\n' cat -n app/kb/kb.ts | sed -n '1,320p' printf '\n## search for path sanitization / dynamicParams under app/kb\n' rg -n "dynamicParams|resolvePath|getArticle|getArticlesByCategory|notFound\\(|\\.join\\(KB_CONTENT_PATH" app/kbRepository: NodeByteHosting/website
Length of output: 9892
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n## packages/kb/lib/kb.ts (outline)\n' ast-grep outline packages/kb/lib/kb.ts --view expanded || true printf '\n## packages/kb/lib/kb.ts (selected lines 1-360)\n' cat -n packages/kb/lib/kb.ts | sed -n '1,360p' printf '\n## search for path validation in packages/kb/lib/kb.ts\n' rg -n "resolvePath|getCategoryAtPath|getArticlesByCategory|getArticle|path\\.join|normalize|sanitize|\\.\\. |\\.\\.\\/|\\0|sep" packages/kb/lib/kb.tsRepository: NodeByteHosting/website
Length of output: 16646
Validate KB path segments in
app/kb/[...path]/page.tsx
The catch-all params flow straight intoresolvePath/getArticle/getArticlesByCategory, whichpath.join(...)them againstKB_CONTENT_PATH. Reject../path-separator segments here, or setdynamicParams = falseif only prebuilt paths should resolve.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/kb/`[...path]/page.tsx around lines 111 - 115, The KB catch-all route in KBDynamicPage currently forwards raw path segments from params into resolvePath and downstream article/category lookup, so add validation before resolvePath to reject any segments containing path traversal or separator characters such as .., /, or \. If this page should only serve known routes, alternatively set dynamicParams = false; otherwise ensure only safe, normalized segments can reach getArticle and getArticlesByCategory.packages/core/constants/product-overrides.ts (1)
1-9: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Documented
SERIES_LOCATIONmap is missing; VPSlocationis hardcoded toundefined.The header comment describes a
SERIES_LOCATIONexport mapping series codes to datacenter cities, andgetVpsPlansinpackages/core/products/billing-service.tsdocuments the same ("Series→location mapping ... come from product-overrides.ts"). Neither this file norbilling-service.tsactually implements it —billing-service.tsline 140 setslocation: undefinedunconditionally, so every VPS plan silently loses its datacenter location.Either this constant was dropped during refactor, or the doc comments are stale. If location display is expected on the VPS pages, this needs to be added.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/constants/product-overrides.ts` around lines 1 - 9, The documented SERIES_LOCATION mapping is missing, and getVpsPlans currently hardcodes VPS location to undefined, so the datacenter city is never populated. Add the SERIES_LOCATION export in product-overrides.ts and use it in billing-service.ts within getVpsPlans to derive location from the plan’s series code instead of assigning undefined. Make sure the new mapping is referenced consistently by the existing series/location doc comments.packages/core/lib/spec-parser.ts (2)
106-120: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Bandwidth parsing only recognizes TB, silently turning GB allowances into "unlimited".
ParsedSpecs["bandwidth"]supports"MB" | "GB" | "TB", but every regex here (bwTbMatch) only matches\d+\s+TB. If a plan's description states its allowance in GB (e.g. "500 GB outbound traffic"), none of the patterns match,bwTbMatchisnull, and the code falls through tobandwidth: null— which downstream treats as unmetered/unlimited. That's a materially wrong claim to surface on a billing page for a metered plan.🔧 Proposed fix
const bwTbMatch = text.match(/(\d+)\s+TB\s+(?:included\s+)?outbound/i) || text.match(/(\d+)\s+TB\s+of\s+(?:poolable\s+)?outbound/i) || text.match(/(\d+)\s+TB\s+Outbound/i) || text.match(/(\d+)\s+TB\b[^.]*?(?:traffic|bandwidth)/i) + const bwGbMatch = !bwTbMatch && ( + text.match(/(\d+)\s+GB\s+(?:included\s+)?outbound/i) || + text.match(/(\d+)\s+GB\s+of\s+(?:poolable\s+)?outbound/i) || + text.match(/(\d+)\s+GB\s+Outbound/i) + ) const bandwidth: ParsedSpecs["bandwidth"] = unlimitedBw ? null : bwTbMatch ? { amount: parseInt(bwTbMatch[1]), unit: "TB" } + : bwGbMatch + ? { amount: parseInt(bwGbMatch[1]), unit: "GB" } : null📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.// ── Bandwidth ────────────────────────────────────────────────────────────── // "1 TB included outbound", "20 TB Outbound Traffic Pool", "4 TB of poolable outbound" // "Unlimited Outbound Bandwidth" → null (unmetered) // Avoids false positives from storage descriptions ("4 x 16 TB Enterprise SATA HDDs") const unlimitedBw = /unlimited\s+(?:\w+\s+)?bandwidth/i.test(text) const bwTbMatch = text.match(/(\d+)\s+TB\s+(?:included\s+)?outbound/i) || text.match(/(\d+)\s+TB\s+of\s+(?:poolable\s+)?outbound/i) || text.match(/(\d+)\s+TB\s+Outbound/i) || text.match(/(\d+)\s+TB\b[^.]*?(?:traffic|bandwidth)/i) const bwGbMatch = !bwTbMatch && ( text.match(/(\d+)\s+GB\s+(?:included\s+)?outbound/i) || text.match(/(\d+)\s+GB\s+of\s+(?:poolable\s+)?outbound/i) || text.match(/(\d+)\s+GB\s+Outbound/i) ) const bandwidth: ParsedSpecs["bandwidth"] = unlimitedBw ? null : bwTbMatch ? { amount: parseInt(bwTbMatch[1]), unit: "TB" } : bwGbMatch ? { amount: parseInt(bwGbMatch[1]), unit: "GB" } : null🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/spec-parser.ts` around lines 106 - 120, The bandwidth parsing in spec-parser.ts only matches TB values, so GB allowances are misclassified as null/unlimited. Update the bandwidth extraction logic in the spec-parser function to recognize GB (and MB if supported by ParsedSpecs["bandwidth"]) alongside TB, and ensure the unit captured from the regex is preserved when constructing the ParsedSpecs["bandwidth"] result. Keep the unlimitedBw check separate so only true unmetered descriptions map to null.
131-138: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Intel branch unconditionally overwrites an already-detected AMD/ARM match.
Line 137's
if (intelMatch)has no guard againstryzenMatch/ampereMatch, unlike its ownelse iffallback on line 138 which does check!ryzenMatch && !ampereMatch. If a description matches both the Ryzen/Ampere pattern and the (fairly broad) Intel pattern — e.g. marketing copy comparing against Intel — the finalhardware/cpuModelsilently becomes"intel", misclassifying the CPU brand shown to customers.🐛 Proposed fix
- if (intelMatch) { cpuModel = intelMatch[0].trim(); hardware = "intel" } + if (intelMatch && !ryzenMatch && !ampereMatch) { cpuModel = intelMatch[0].trim(); hardware = "intel" } else if (/\bintel\b/i.test(text) && !ryzenMatch && !ampereMatch) { hardware = "intel" }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.if (ryzenMatch) { cpuModel = ryzenMatch[0].trim(); hardware = "amd" } else if (/\bamd\b/i.test(text)) { hardware = "amd" } if (ampereMatch) { cpuModel = "Ampere® Altra® ARM64"; hardware = "arm" } else if (/\barm64?\b/i.test(text) && !ryzenMatch) { hardware = "arm" } if (intelMatch && !ryzenMatch && !ampereMatch) { cpuModel = intelMatch[0].trim(); hardware = "intel" } else if (/\bintel\b/i.test(text) && !ryzenMatch && !ampereMatch) { hardware = "intel" }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/lib/spec-parser.ts` around lines 131 - 138, The Intel detection in spec-parser.ts is overriding earlier AMD/ARM matches because the intelMatch branch in the parsing logic does not check whether ryzenMatch or ampereMatch was already found. Update the CPU detection flow in the relevant spec-parser logic so the Intel assignment only runs when no AMD/ARM match has been established, matching the existing guards used in the fallback branches for ryzenMatch and ampereMatch. Keep the cpuModel/hardware precedence consistent within the same detection block.packages/core/products/billing-service.ts (1)
73-86: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Doc comment overstates the validation actually performed.
The function comment claims plans "missing cores/ram/storage" are skipped, but the guard below only checks
!parsed.ramGB.coresandstorageGBcan beundefinedand the plan is still returned (consistent with them being optional onDedicatedPlanSpec). Please update the comment to match the actual (ramGB-only) requirement, or tighten the check if cores/storage really should be mandatory.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/products/billing-service.ts` around lines 73 - 86, The JSDoc for getDedicatedPlans overstates the validation: the implementation only filters out products missing parsed.ramGB, while cores and storageGB may still be undefined and are returned as optional fields in DedicatedPlanSpec. Update the comment to describe the actual ramGB-only requirement, or, if cores/storage should be required, add matching checks in getDedicatedPlans and keep warnDroppedProduct in sync with the rejected fields.packages/core/types/servers/dedicated.ts (1)
20-20: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail printf '\n== files ==\n' git ls-files 'packages/core/types/servers/dedicated.ts' \ 'packages/core/lib/spec-parser.ts' \ 'packages/core/products/billing-service.ts' \ 'packages/core/**/*' | sed -n '1,200p' printf '\n== dedicated type ==\n' sed -n '1,120p' packages/core/types/servers/dedicated.ts printf '\n== spec parser slice ==\n' sed -n '1,220p' packages/core/lib/spec-parser.ts printf '\n== billing service slice ==\n' sed -n '1,180p' packages/core/products/billing-service.ts printf '\n== searches ==\n' rg -n 'DedicatedPlanSpec\\[\"hardware\"\\]|hardware: parsed\\.hardware|hardware\\?: "amd" \\| "intel" \\| "arm"|brand logo|logo.*hardware|switch.*hardware' packages/core -SRepository: NodeByteHosting/website
Length of output: 15810
🏁 Script executed:
#!/bin/bash set -euo pipefail python3 - <<'PY' print("noop") PYRepository: NodeByteHosting/website
Length of output: 166
Add
"arm"toDedicatedPlanSpec.hardware
packages/core/lib/spec-parser.tscan emithardware: "arm", andpackages/core/products/billing-service.tsforwards that value intoDedicatedPlanSpecvia a type assertion. As written, dedicated plans can carry an ARM hardware value at runtime while the type still only allows"amd" | "intel".🔧 Proposed fix
- hardware?: "amd" | "intel" + hardware?: "amd" | "intel" | "arm"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.hardware?: "amd" | "intel" | "arm"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/types/servers/dedicated.ts` at line 20, DedicatedPlanSpec.hardware is missing the "arm" value even though spec parsing and billing already pass it through at runtime. Update the DedicatedPlanSpec type in the dedicated server type definition to include "arm" alongside the existing hardware options, and make sure any related unions or assertions that reference hardware stay aligned with the expanded set.packages/kb/components/kb-sidebar.tsx (1)
43-44: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Prefix-based active check can highlight sibling categories.
pathname.startsWith(categoryPrefix)treats any category whose path is a string prefix of another as active. For sibling categories likegameandgames, visiting/kb/games/...also marksgameactive. Anchor the match to a segment boundary while preserving ancestor highlighting.🐛 Proposed fix
const categoryPrefix = `/kb/${item.path}` - const isCategoryActive = pathname.startsWith(categoryPrefix) + const isCategoryActive = + pathname === categoryPrefix || pathname.startsWith(`${categoryPrefix}/`)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const categoryPrefix = `/kb/${item.path}` const isCategoryActive = pathname === categoryPrefix || pathname.startsWith(`${categoryPrefix}/`)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/kb/components/kb-sidebar.tsx` around lines 43 - 44, The active-state check in kb-sidebar’s category matching is too broad because pathname.startsWith(categoryPrefix) can mark sibling categories like game active when visiting games. Update the logic in kb-sidebar.tsx around categoryPrefix/isCategoryActive to require a segment boundary after the category path (while still allowing ancestor paths to remain active), so only the exact category or its descendants match.packages/kb/lib/kb.ts (2)
168-179: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
order || 999drops legitimateorder: 0, mis-sorting the category.
meta.orderis set viafileMeta.order ?? 999, so an explicitorder: 0(e.g.content/billing/_meta.json) is preserved as0. But the comparator usesa.order || 999, which coerces0 → 999, pushing that category to the bottom instead of the top. Sinceorderis always a number here, drop the||fallback.🐛 Proposed fix
- return categories.sort((a, b) => (a.order || 999) - (b.order || 999)) + return categories.sort((a, b) => a.order - b.order)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.categories.push({ slug: dir.name, path: catPath, parentPath, ...meta, articleCount: directArticleCount, totalCount, subcategories, }) } return categories.sort((a, b) => a.order - b.order)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/kb/lib/kb.ts` around lines 168 - 179, The category sorting in kb.ts is incorrectly treating order 0 as missing because the comparator uses a falsy fallback. Update the sort logic in the return from the category-building function to use a numeric-safe fallback that preserves legitimate zero values, and keep the change localized to the categories.sort comparator so explicit order values from meta are respected.
244-248: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Same
order: 0sorting bug for articles.
order: data.order ?? 999preserves0, but the comparator(a.order || 999)coerces0 → 999, so an article withorder: 0sorts last. This also skews prev/next navigation in the catch-all page, which relies on this ordering. Use a plain numeric comparator.🐛 Proposed fix
- return articles.sort((a, b) => (a.order || 999) - (b.order || 999)) + return articles.sort((a, b) => a.order - b.order)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.order: data.order ?? 999, }) } return articles.sort((a, b) => a.order - b.order)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/kb/lib/kb.ts` around lines 244 - 248, The articles sorting logic in kb.ts still treats order 0 as 999 in the comparator, so items with a valid zero order are placed last and break prev/next navigation. Update the sorting in the article list builder that returns articles.sort(...) to use a numeric comparison based on the actual order values without falling back on truthiness, so order: 0 is preserved consistently with the existing data.order ?? 999 assignment.packages/ui/components/Layouts/Nodes/nodes-client.tsx (1)
307-317: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Aggregate "In Maintenance" stat misrepresents degraded/down nodes.
maintenanceCountis derived asnodes.length - onlineCount, so any node whose live status is"degraded"or"down"(not"up") gets folded into "In Maintenance" in the hero stats, even though its ownNodeCardbadge correctly shows "Degraded"/"Offline". This creates a user-visible inconsistency between the per-node state and the aggregate count.🔧 Suggested fix using resolveNodeState for consistent bucketing
- const nodeLiveStates = nodes.map((node) => { - const monitorName = NODE_MONITOR_MAP[node.name] - return monitorName ? findMonitor(monitorName) : null - }) - const onlineCount = nodeLiveStates.filter( - (live, i) => (live ? live.status === "up" : !nodes[i].isMaintenanceMode), - ).length - const maintenanceCount = nodes.length - onlineCount + const nodeLiveStates = nodes.map((node) => { + const monitorName = NODE_MONITOR_MAP[node.name] + return monitorName ? findMonitor(monitorName) : null + }) + const nodeStates = nodes.map((node, i) => resolveNodeState(node, nodeLiveStates[i])) + const onlineCount = nodeStates.filter((s) => s.label === "Online").length + const maintenanceCount = nodeStates.filter((s) => s.label === "Maintenance").lengthAlso applies to: 350-354
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Layouts/Nodes/nodes-client.tsx` around lines 307 - 317, The aggregate “In Maintenance” stat is using a simple onlineCount subtraction in NodesClient, which incorrectly buckets degraded/down nodes as maintenance. Update the hero stat calculation to derive counts from the same node-state logic used by each NodeCard, ideally via resolveNodeState, so only true maintenance nodes are counted there and degraded/offline nodes remain separate. Keep the aggregation consistent wherever maintenanceCount is computed so the summary matches the per-node badges.packages/ui/components/Static/footer.tsx (1)
122-125: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
"System Status" footer link likely points to the wrong URL.
The Services list labels this link "System Status" but hardcodes
https://lg.nodebyte.host(the network looking-glass, matchingLINKS.network), not the actual status page (LINKS.status→https://status.nodebyte.host) thatStatusBadgein this same file correctly links to. Also, it's hardcoded instead of using the already-importedLINKSconstant like the rest of this file.🔧 Proposed fix
{ href: "/vps", label: t("footer.services.vpsServers") }, { href: "/games", label: t("footer.services.gameServers") }, { href: "/dedicated", label: t("footer.services.dedicatedServers") }, - { href: "https://lg.nodebyte.host", label: t("footer.services.systemStatus") }, + { href: LINKS.status, label: t("footer.services.systemStatus") },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.{ href: "/vps", label: t("footer.services.vpsServers") }, { href: "/games", label: t("footer.services.gameServers") }, { href: "/dedicated", label: t("footer.services.dedicatedServers") }, { href: LINKS.status, label: t("footer.services.systemStatus") },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/components/Static/footer.tsx` around lines 122 - 125, The footer Services entry for `t("footer.services.systemStatus")` is hardcoded to the looking-glass URL instead of the actual status page. Update the link in `Footer` to use the existing `LINKS.status` constant, matching the `StatusBadge` behavior in this file and avoiding the hardcoded `https://lg.nodebyte.host` value.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation