Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughSwitches the docs site to the VoidZero VitePress theme, adds a slot-forwarding Layout, enhances Card/CardGroup components, implements dark-mode pre-hydration sync and runtime sign-in CTA relocation, updates styles and TypeScript/VSCode configs, and converts the homepage to inline component usage. Changes
Sequence Diagram(s)sequenceDiagram
participant App as VitePress App
participant Theme as VoidZeroTheme
participant Layout as Plane Layout
participant DOM as Document / Header
participant Router as Router
App->>Theme: enhanceApp(ctx)
Theme->>App: register components & provide planeThemeContext
App->>Layout: mount Layout
Layout->>DOM: locate header & sign-in CTA
DOM-->>Layout: sign-in present / absent
Layout->>DOM: relocate CTA into header (with retries & hidden state)
Layout->>DOM: apply/remove `dark` class and `data-theme="dark"` (sync from isDark/localStorage)
Router->>Layout: on route change -> trigger relocate and theme sync
DOM->>Layout: MutationObserver/resize -> debounce relocate attempts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
docs/.vitepress/theme/components/Card.vue (1)
1-1:⚠️ Potential issue | 🟡 MinorFix the formatting failure before merge.
CI reports
oxfmt --checkfailures for this file; please runpnpm fix:formatand confirmpnpm check:formatpasses. As per coding guidelines,Run pnpm fix:format before committing and ensure CI check via pnpm check:format passes; formatting is enforced.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/components/Card.vue` at line 1, CI flagged a formatting error in docs/.vitepress/theme/components/Card.vue; run the project's formatter and commit the result by running the repository scripts to fix and verify formatting (execute pnpm fix:format then pnpm check:format) and ensure the updated Card.vue (the <script setup> block) is saved/committed so the CI oxfmt --check passes.package.json (1)
31-35:⚠️ Potential issue | 🟠 MajorAlign Node typings with the declared runtime floor.
@types/nodeis pinned to version 25 major, butengines.nodespecifies>=24.0.0. This mismatch allows Node 25-only APIs to pass type-checking while failing at runtime on Node 24. Align the versions by using Node 24 typings or raising the minimum Node version.Proposed fix
- "@types/node": "^25.6.0", + "@types/node": "^24.0.0",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` around lines 31 - 35, The `@types/node` dev dependency (symbol: "@types/node") does not match the declared runtime floor (symbol: "engines.node"), causing potential type-checks for Node 25 APIs while the runtime may be Node 24; fix by either downgrading "@types/node" to a 24.x major (e.g., "^24.0.0") to match "engines.node": ">=24.0.0" or by raising "engines.node" to ">=25.0.0" so it aligns with the installed "@types/node" major—update package.json accordingly and run a reinstall/type-check to verify no new type/runtime mismatches remain.
🧹 Nitpick comments (2)
docs/.vitepress/theme/index.ts (1)
129-139: Verify slot forwarding behavior with VoidZeroTheme defaults.The
Layout.vuefile usesuseSlots()withObject.keys(slots)to forward slots, which only forwards slots the parent actively provides. IfVoidZeroTheme.Layouthas default content for slots likehome-hero-beforethat aren't explicitly overridden, those defaults won't render. Test with a commonly-used VoidZero slot to confirm the theme's default fallbacks still appear when not overridden, or explicitly enumerate known slots from the base theme to ensure all expected defaults are preserved.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 129 - 139, The Layout enhancement currently forwards only slots provided by the parent (via useSlots/Object.keys) and can suppress VoidZeroTheme's default slot content (e.g., home-hero-before); update enhanceApp/Layout handling to ensure theme defaults are preserved by forwarding either the full list of known VoidZeroTheme slots or delegating slot forwarding to VoidZeroTheme.Layout: locate the Layout component and its slot forwarding logic (referencing Layout and VoidZeroTheme), then replace Object.keys(slots) usage with an explicit array of base-theme slot names (include commonly used names such as "home-hero-before", "home-hero-after", etc.) or call through to VoidZeroTheme.Layout's slot resolution so default content in the base theme still appears when parents don’t override those slots.docs/.vitepress/theme/style.css (1)
699-709: CSS selectors unnecessarily couple to Tailwind utility class names.The rules targeting
a[href="/"].flex.flex-colandimg.h-4will silently break if the upstream@voidzero-dev/vitepress-theme(currently ^4.8.4) changes how it renders the OSSHeader logo markup—for instance, swappingflex-colforflex-roworh-4for a different size utility. No build-time error signals the breakage.Consider requesting stable customization hooks from the theme (e.g., a custom class or
data-*attribute on the logo container), or refactor these rules to target onlyheader a[href="/"]and overrideflex-direction/heightwithout depending on the presence of specific utility classes. If the theme provides no documented override point, this workaround may be necessary for now.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/style.css` around lines 699 - 709, The CSS selectors a[href="/"].flex.flex-col and img.h-4 are too tightly coupled to upstream Tailwind utilities and will break if the theme changes those classes; update the selectors to target only the stable elements (e.g., header a[href="/"] and header a[href="/"] img) and apply the needed overrides (flex-direction, align-items, gap, height) without relying on .flex .flex-col or .h-4; if the theme exposes a stable hook (class or data-*), switch selectors to that hook instead and document the change so OSSHeader styling is resilient to markup/utility-class changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/.vitepress/theme/components/Card.vue`:
- Line 18: The computed resolvedHref currently falls back to "#" which makes
non-link cards act like anchors; change the computed (resolvedHref) to return
null/undefined when neither props.link nor props.href are provided, and update
the template logic that renders the card (where an anchor element is used) to
render a non-anchor wrapper (e.g., a div/button or plain element) when
resolvedHref is falsy; ensure you reference props.link and props.href in the new
computed and adjust the conditional rendering used around the anchor (the same
spots that currently use resolvedHref) so informational cards are not clickable
nor navigate to "#" anymore.
In `@docs/.vitepress/theme/index.ts`:
- Around line 97-127: The relocation logic in moveSignInToUtilityArea is
race-prone and brittle (first-paint, header re-renders, selector changes);
update it to reliably relocate the sign-in link by observing the header DOM
until the target appears: use a MutationObserver on the header (or a short retry
loop) to look for the sign-in anchor and the appearance/extra menu elements,
perform the insertBefore and add the "sign-in-relocated" class once, then
disconnect the observer; also add a debounced window resize handler to re-run
the relocation with a guard to avoid repeated work, and emit a console.warn when
the function bails because selectors are not found so regressions are visible
(reference moveSignInToUtilityArea, ".sign-in-relocated", and the
appearance/extra menu selectors).
- Around line 132-139: The call to VoidZeroTheme.enhanceApp is currently invoked
directly in enhanceApp(ctx) and can throw if that method is undefined in future
versions; change the call to use optional chaining
(VoidZeroTheme.enhanceApp?.(ctx)) to guard against undefined, and optionally
move ctx.app.provide(themeContextKey, planeThemeContext) to after the
VoidZeroTheme.enhanceApp?.(ctx) call so the child theme's provided value cannot
be overwritten by the base theme; update the enhanceApp function accordingly
(refer to enhanceApp, VoidZeroTheme.enhanceApp, ctx.app.provide,
themeContextKey, planeThemeContext).
In `@docs/.vitepress/theme/style.css`:
- Around line 749-753: The CSS rule .docs-layout header
.VPLink[href*="sign-in"]:not(.sign-in-relocated) currently hides the Sign-in
link for no-JS or failed-JS cases; update the relocation flow so the link is
visible by default and only hidden while awaiting relocation: either (A) add an
initial "not-yet-relocated" marker class to the Sign-in element server-side or
from your startup JS and have moveSignInToUtilityArea() remove that marker and
add .sign-in-relocated, or (B) narrow the CSS selector to target only the
in-menu container (e.g., scope to .VPNavBarMenu
.VPLink[href*="sign-in"]:not(.sign-in-relocated)) so the link remains reachable
if relocation fails; modify the code that manipulates .sign-in-relocated in
index.ts (moveSignInToUtilityArea) accordingly.
- Around line 612-616: The selector uses the Vue SFC scoped-styles pseudo-class
:deep which is invalid in a global CSS file; update the rule targeting .vp-doc
.home-feature-cards .card-icon :deep(svg) to target the SVG directly (remove the
:deep wrapper) so the browser sees a valid selector and keep the
width/height/flex-shrink declarations for the svg element.
In `@docs/index.md`:
- Line 1: The repo-wide formatting failed for docs/index.md; run the project
formatter and checks: execute pnpm fix:format to apply oxfmt formatting changes
to docs/index.md, commit the updated file, and verify pnpm check:format passes
in your local CI; ensure you do not modify content beyond formatting and include
the formatted docs/index.md in the same commit so the CI oxfmt --check will
succeed.
- Line 11: The H1 currently includes the tagline ("# Plane Documentation Plan,
track, and ship your work with Plane.") — split them by making the H1 just the
title (e.g., "# Plane Documentation") and move the tagline ("Plan, track, and
ship your work with Plane.") into its own body paragraph directly under the H1
in docs/index.md so the heading and tagline render separately.
---
Outside diff comments:
In `@docs/.vitepress/theme/components/Card.vue`:
- Line 1: CI flagged a formatting error in
docs/.vitepress/theme/components/Card.vue; run the project's formatter and
commit the result by running the repository scripts to fix and verify formatting
(execute pnpm fix:format then pnpm check:format) and ensure the updated Card.vue
(the <script setup> block) is saved/committed so the CI oxfmt --check passes.
In `@package.json`:
- Around line 31-35: The `@types/node` dev dependency (symbol: "@types/node") does
not match the declared runtime floor (symbol: "engines.node"), causing potential
type-checks for Node 25 APIs while the runtime may be Node 24; fix by either
downgrading "@types/node" to a 24.x major (e.g., "^24.0.0") to match
"engines.node": ">=24.0.0" or by raising "engines.node" to ">=25.0.0" so it
aligns with the installed "@types/node" major—update package.json accordingly
and run a reinstall/type-check to verify no new type/runtime mismatches remain.
---
Nitpick comments:
In `@docs/.vitepress/theme/index.ts`:
- Around line 129-139: The Layout enhancement currently forwards only slots
provided by the parent (via useSlots/Object.keys) and can suppress
VoidZeroTheme's default slot content (e.g., home-hero-before); update
enhanceApp/Layout handling to ensure theme defaults are preserved by forwarding
either the full list of known VoidZeroTheme slots or delegating slot forwarding
to VoidZeroTheme.Layout: locate the Layout component and its slot forwarding
logic (referencing Layout and VoidZeroTheme), then replace Object.keys(slots)
usage with an explicit array of base-theme slot names (include commonly used
names such as "home-hero-before", "home-hero-after", etc.) or call through to
VoidZeroTheme.Layout's slot resolution so default content in the base theme
still appears when parents don’t override those slots.
In `@docs/.vitepress/theme/style.css`:
- Around line 699-709: The CSS selectors a[href="/"].flex.flex-col and img.h-4
are too tightly coupled to upstream Tailwind utilities and will break if the
theme changes those classes; update the selectors to target only the stable
elements (e.g., header a[href="/"] and header a[href="/"] img) and apply the
needed overrides (flex-direction, align-items, gap, height) without relying on
.flex .flex-col or .h-4; if the theme exposes a stable hook (class or data-*),
switch selectors to that hook instead and document the change so OSSHeader
styling is resilient to markup/utility-class changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dcce00e2-11a6-4df6-982e-961846857dce
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
.vscode/settings.jsondocs/.vitepress/config.tsdocs/.vitepress/theme/Layout.vuedocs/.vitepress/theme/components/Card.vuedocs/.vitepress/theme/components/CardGroup.vuedocs/.vitepress/theme/index.tsdocs/.vitepress/theme/style.cssdocs/.vitepress/types/modules.d.tsdocs/index.mdpackage.jsontsconfig.json
…ith VitePress's isDark state
There was a problem hiding this comment.
🧹 Nitpick comments (2)
docs/.vitepress/theme/index.ts (1)
244-263: Symmetric cleanup for the poll-fallback interval and thehashchangelistener.The rest of this
onMountedblock is careful about teardown (headerObserver?.disconnect(), resize listener removed), but two hooks leak past unmount in principle:
- The
setInterval(id, 120)/ 5ssetTimeout(...)fallback on Lines 245-251 capturesidin closure; neither is tracked foronUnmounted.- The
hashchangehandler added on Lines 260-262 uses an anonymous callback and is never removed.In practice the theme
setup()only runs once per app load so this isn't user-visible, but mirroring the cleanup pattern you already established avoids surprise if this code is ever reused in a more dynamic host (HMR, Storybook, tests).🧹 Suggested cleanup
- const onHeaderMutations = debounce(() => { + const onHeaderMutations = debounce(() => { runSignInRelocationWithRetries(); }, 100); const tryAttachHeaderObserver = () => { if (headerObserver) return; const h = document.querySelector(".docs-layout header"); if (!h) return; headerObserver = new MutationObserver(onHeaderMutations); headerObserver.observe(h, { childList: true, subtree: true }); }; tryAttachHeaderObserver(); + let pollIntervalId: number | undefined; + let pollTimeoutId: number | undefined; if (!headerObserver) { - const id = window.setInterval(() => { + pollIntervalId = window.setInterval(() => { tryAttachHeaderObserver(); if (headerObserver) { - clearInterval(id); + if (pollIntervalId !== undefined) clearInterval(pollIntervalId); } }, 120); - window.setTimeout(() => clearInterval(id), 5000); + pollTimeoutId = window.setTimeout(() => { + if (pollIntervalId !== undefined) clearInterval(pollIntervalId); + }, 5000); } onResize = debounce(() => { runSignInRelocationWithRetries(); }, 150); window.addEventListener("resize", onResize); - // Listen for hash changes - window.addEventListener("hashchange", () => { - nextTick(handleTabHash); - }); + onHashChange = () => { + nextTick(handleTabHash); + }; + window.addEventListener("hashchange", onHashChange); }); onUnmounted(() => { headerObserver?.disconnect(); headerObserver = null; if (onResize) { window.removeEventListener("resize", onResize); onResize = null; } + if (onHashChange) { + window.removeEventListener("hashchange", onHashChange); + onHashChange = null; + } + if (pollIntervalId !== undefined) clearInterval(pollIntervalId); + if (pollTimeoutId !== undefined) clearTimeout(pollTimeoutId); });(Declare
let onHashChange: (() => void) | null = null;alongside the existinglet headerObserver/let onResizedeclarations.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 244 - 263, The poll-fallback interval/timeout and the anonymous hashchange listener are never cleaned up on unmount; declare tracking variables (e.g., let pollId: number | null = null, let pollTimeoutId: number | null = null, and let onHashChange: (() => void) | null = null) alongside headerObserver and onResize, assign pollId/pollTimeoutId when calling window.setInterval/window.setTimeout and assign the hash handler to onHashChange (call nextTick(handleTabHash) inside it), then in the existing onUnmounted cleanup block clearInterval(pollId), clearTimeout(pollTimeoutId), and removeEventListener("hashchange", onHashChange) (and keep removing the resize listener via onResize) so all resources (headerObserver, resize, poll interval/timeout, and hashchange) are torn down symmetrically.docs/.vitepress/theme/style.css (1)
699-709: Consider using stable selectors instead of Tailwind utility classes for OSSHeader overrides.The CSS rules coupling to
.flex.flex-coland.h-4depend on the exact Tailwind utility classes emitted by@voidzero-dev/vitepress-theme's OSSHeader component. Since the package uses semver^4.8.4, minor version updates (e.g.,4.9.x) can change component markup without breaking the version constraint. If OSSHeader's classes change, these rules will silently no-op, reverting the logo to its stacked layout.Use more stable selectors instead: scope via
[class*="Logo"]or target the anchor byaria-labelattribute. Alternatively, pin@voidzero-dev/vitepress-themeto a specific version so these coupled rules are revisited and tested at upgrade time.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/style.css` around lines 699 - 709, The current CSS targets fragile Tailwind utilities `.docs-layout header a[href="/"].flex.flex-col` and `.docs-layout header a[href="/"] img.h-4` which may break if OSSHeader markup changes; update these selectors to stable ones (e.g., target the OSSHeader anchor via an ARIA attribute like `a[aria-label="Home"]` or a class name pattern such as `[class*="Logo"]` and adjust the image selector accordingly) so the layout override survives minor theme updates, or alternatively pin the `@voidzero-dev/vitepress-theme` dependency to a fixed patch version to ensure these utility classes are re-evaluated on upgrade; modify the selectors referenced above (the anchor and image rules) to use the chosen stable selector approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@docs/.vitepress/theme/index.ts`:
- Around line 244-263: The poll-fallback interval/timeout and the anonymous
hashchange listener are never cleaned up on unmount; declare tracking variables
(e.g., let pollId: number | null = null, let pollTimeoutId: number | null =
null, and let onHashChange: (() => void) | null = null) alongside headerObserver
and onResize, assign pollId/pollTimeoutId when calling
window.setInterval/window.setTimeout and assign the hash handler to onHashChange
(call nextTick(handleTabHash) inside it), then in the existing onUnmounted
cleanup block clearInterval(pollId), clearTimeout(pollTimeoutId), and
removeEventListener("hashchange", onHashChange) (and keep removing the resize
listener via onResize) so all resources (headerObserver, resize, poll
interval/timeout, and hashchange) are torn down symmetrically.
In `@docs/.vitepress/theme/style.css`:
- Around line 699-709: The current CSS targets fragile Tailwind utilities
`.docs-layout header a[href="/"].flex.flex-col` and `.docs-layout header
a[href="/"] img.h-4` which may break if OSSHeader markup changes; update these
selectors to stable ones (e.g., target the OSSHeader anchor via an ARIA
attribute like `a[aria-label="Home"]` or a class name pattern such as
`[class*="Logo"]` and adjust the image selector accordingly) so the layout
override survives minor theme updates, or alternatively pin the
`@voidzero-dev/vitepress-theme` dependency to a fixed patch version to ensure
these utility classes are re-evaluated on upgrade; modify the selectors
referenced above (the anchor and image rules) to use the chosen stable selector
approach.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bbb83923-feaa-4e26-8d3e-6d1c824ec11b
📒 Files selected for processing (4)
docs/.vitepress/theme/components/Card.vuedocs/.vitepress/theme/index.tsdocs/.vitepress/theme/style.cssdocs/index.md
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/index.md
- docs/.vitepress/theme/components/Card.vue
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
docs/.vitepress/theme/index.ts (2)
275-287:hashchangelistener isn't cleaned up.The
resizelistener andMutationObserverare both torn down inonUnmounted, but the inlinehashchangehandler added on Line 275 is never removed. It is unlikely to cause user-visible issues since the themesetup()is called once per app, but HMR cycles in dev and any future teardown path will accumulate duplicate listeners.🔧 Suggested fix
+ const onHashChange = () => { + nextTick(handleTabHash); + }; - // Listen for hash changes - window.addEventListener("hashchange", () => { - nextTick(handleTabHash); - }); + // Listen for hash changes + window.addEventListener("hashchange", onHashChange); }); onUnmounted(() => { headerObserver?.disconnect(); headerObserver = null; if (onResize) { window.removeEventListener("resize", onResize); onResize = null; } + window.removeEventListener("hashchange", onHashChange); });(
onHashChangewill need to be hoisted to a scope visible to both hooks.)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 275 - 287, The hashchange listener added via window.addEventListener("hashchange", () => { nextTick(handleTabHash); }) is not removed in onUnmounted; hoist the handler into a named function/const (e.g., onHashChange) in the outer setup scope so it can be referenced in both the add and remove calls, replace the inline arrow with that onHashChange when adding the listener, and call window.removeEventListener("hashchange", onHashChange) inside onUnmounted alongside headerObserver.disconnect() and the resize cleanup.
239-272: Minor: overlapping retry chains from multiple triggers.
runSignInRelocationWithRetriesis invoked from the initialsetTimeout, every debounced mutation, every debounced resize, and each route change.moveSignInToUtilityAreais idempotent (short-circuits on.sign-in-relocated), so nothing breaks, but in the worst case several independent 40×75ms retry chains can run concurrently. Consider a single in-flight flag or clearing any pending retry before scheduling the next chain to avoid redundant timers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 239 - 272, Multiple concurrent retry chains are started because runSignInRelocationWithRetries is called from the initial setTimeout, mutation debounce, resize debounce, and route change; to fix, add a single in-flight guard or a cancel mechanism inside runSignInRelocationWithRetries (or a new wrapper like scheduleSignInRelocation) that tracks a pendingRetry boolean/timeoutId and returns early if one is active (or clears the existing timeout before scheduling a new chain), reference the existing runSignInRelocationWithRetries and moveSignInToUtilityArea functions and update callers (the setTimeout initializer, the debounce callbacks that call runSignInRelocationWithRetries, and the route-change invoker) to use the guarded/wrapper function so only one retry chain runs at a time.docs/.vitepress/theme/style.css (1)
838-862: Consider consolidating the repeated:has()selector.The long compound selector
.VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds))is repeated verbatim across three rules. A small refactor (CSS nesting, or a single rule with a comma-joined selector list for the child targets) improves maintainability and avoids drift if one branch is updated in isolation later.♻️ Proposed refactor (comma-joined targets)
- .docs-layout - .VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds)) - .aside { - display: none !important; - flex: 0 0 0 !important; - width: 0 !important; - min-width: 0 !important; - max-width: 0 !important; - margin: 0 !important; - padding: 0 !important; - overflow: hidden !important; - } - - .docs-layout - .VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds)) - .content-container { - max-width: 100% !important; - } - - .docs-layout - .VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds)) - .content { - max-width: 100% !important; - } + .docs-layout + .VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds)) { + & .aside { + display: none !important; + flex: 0 0 0 !important; + width: 0 !important; + min-width: 0 !important; + max-width: 0 !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + } + & .content-container, + & .content { + max-width: 100% !important; + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/style.css` around lines 838 - 862, The three rules repeat the long compound selector `.docs-layout .VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds))` for `.aside`, `.content-container`, and `.content`; consolidate by creating a single rule for that parent selector and target the three child selectors together (or use CSS nesting) so the shared selector exists only once, keeping the same declarations for `.aside` and the max-width rules for `.content-container` and `.content`; reference the selectors `.docs-layout`, `.VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds))`, and the child targets `.aside`, `.content-container`, `.content` when applying the refactor.docs/.vitepress/config.ts (1)
206-222: Small simplification:!pbranch is unreachable.Because
p = localStorage.getItem(k) || "dark"guaranteespis a non-empty string,!pind=!p||p==="auto"?m:p==="dark"is always false. Not a bug — the logic is still correct — but the expression can be simplified for clarity.♻️ Proposed simplification
-var p=localStorage.getItem(k)||"dark"; +var p=localStorage.getItem(k); var m=matchMedia("(prefers-color-scheme: dark)").matches; -var d=!p||p==="auto"?m:p==="dark"; +var d=!p||p==="auto"?m:p==="dark"; /* default to system when no preference stored */Or, keep the default and drop
!p:-var p=localStorage.getItem(k)||"dark"; -var d=!p||p==="auto"?m:p==="dark"; +var p=localStorage.getItem(k)||"dark"; +var d=p==="auto"?m:p==="dark";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/config.ts` around lines 206 - 222, The ternary that computes d uses an unreachable condition (!p) because p is set via p = localStorage.getItem(k) || "dark" and thus always a non-empty string; simplify d calculation by removing the !p branch and change d=!p||p==="auto"?m:p==="dark" to a clearer expression that checks p === "auto" first (e.g., p === "auto" ? m : p === "dark") so the variables p and d inside the inline IIFE and function bar remain correct and behavior is unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/.vitepress/config.ts`:
- Around line 225-232: The themeConfig currently sets variant: "voidzero", which
is unsupported by `@voidzero-dev/vitepress-theme`; update the variant property
inside themeConfig to one of the supported values ("vite", "viteplus", "vitest",
"rolldown", or "oxc") that best matches your project (e.g., change variant in
themeConfig from "voidzero" to "vite" or another supported option) so the theme
renders and builds correctly.
In `@docs/.vitepress/theme/index.ts`:
- Line 1: Formatting failed for the module that contains the import line "import
type { Theme } from \"vitepress\""; run the repository formatter and commit the
result: execute pnpm fix:format, verify the formatter fixed the file, then run
pnpm check:format to ensure CI will pass and commit the formatted changes
(ensure the file with the import is included in the commit).
---
Nitpick comments:
In `@docs/.vitepress/config.ts`:
- Around line 206-222: The ternary that computes d uses an unreachable condition
(!p) because p is set via p = localStorage.getItem(k) || "dark" and thus always
a non-empty string; simplify d calculation by removing the !p branch and change
d=!p||p==="auto"?m:p==="dark" to a clearer expression that checks p === "auto"
first (e.g., p === "auto" ? m : p === "dark") so the variables p and d inside
the inline IIFE and function bar remain correct and behavior is unchanged.
In `@docs/.vitepress/theme/index.ts`:
- Around line 275-287: The hashchange listener added via
window.addEventListener("hashchange", () => { nextTick(handleTabHash); }) is not
removed in onUnmounted; hoist the handler into a named function/const (e.g.,
onHashChange) in the outer setup scope so it can be referenced in both the add
and remove calls, replace the inline arrow with that onHashChange when adding
the listener, and call window.removeEventListener("hashchange", onHashChange)
inside onUnmounted alongside headerObserver.disconnect() and the resize cleanup.
- Around line 239-272: Multiple concurrent retry chains are started because
runSignInRelocationWithRetries is called from the initial setTimeout, mutation
debounce, resize debounce, and route change; to fix, add a single in-flight
guard or a cancel mechanism inside runSignInRelocationWithRetries (or a new
wrapper like scheduleSignInRelocation) that tracks a pendingRetry
boolean/timeoutId and returns early if one is active (or clears the existing
timeout before scheduling a new chain), reference the existing
runSignInRelocationWithRetries and moveSignInToUtilityArea functions and update
callers (the setTimeout initializer, the debounce callbacks that call
runSignInRelocationWithRetries, and the route-change invoker) to use the
guarded/wrapper function so only one retry chain runs at a time.
In `@docs/.vitepress/theme/style.css`:
- Around line 838-862: The three rules repeat the long compound selector
`.docs-layout
.VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds))`
for `.aside`, `.content-container`, and `.content`; consolidate by creating a
single rule for that parent selector and target the three child selectors
together (or use CSS nesting) so the shared selector exists only once, keeping
the same declarations for `.aside` and the max-width rules for
`.content-container` and `.content`; reference the selectors `.docs-layout`,
`.VPDoc.has-aside:has(.VPDocAsideOutline:not(.has-outline)):not(:has(.VPDocAsideCarbonAds))`,
and the child targets `.aside`, `.content-container`, `.content` when applying
the refactor.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: af8cc70a-b4f4-4b5f-bb0a-4315aefd3b46
📒 Files selected for processing (4)
docs/.vitepress/config.tsdocs/.vitepress/theme/index.tsdocs/.vitepress/theme/style.cssdocs/index.md
| themeConfig: { | ||
| variant: "voidzero", | ||
| logo: { | ||
| light: "https://media.docs.plane.so/logo/new-logo-white.png", | ||
| dark: "https://media.docs.plane.so/logo/new-logo-dark.png", | ||
| }, | ||
| siteTitle: "", | ||
| siteTitle: "Plane", | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@voidzero-dev/vitepress-theme themeConfig.variant supported values list
💡 Result:
The supported values for themeConfig.variant in @voidzero-dev/vitepress-theme are strings corresponding to VoidZero projects, such as "viteplus", "vite", "vitest". Additional likely values based on project context include "rolldown" and "oxc", as the theme is designed for Vite+, Vite, Vitest, Rolldown, and Oxc. The variant controls branding colors and styling via CSS selectors like :root[data-variant="vite"]. Users import specific variants like '@voidzero-dev/vitepress-theme/src/vite' in the theme entry.
Citations:
- 1: https://www.npmjs.com/package/@voidzero-dev/vitepress-theme
- 2: https://registry.npmjs.org/%40voidzero-dev%2Fvitepress-theme
- 3: https://github.com/vitejs/vite/blob/main/docs/.vitepress/config.ts
Change variant: "voidzero" to a supported value.
The @voidzero-dev/vitepress-theme package only supports variants: "vite", "viteplus", "vitest", "rolldown", and "oxc". The variant "voidzero" is not recognized and will cause incorrect theme rendering or build failures. Use one of the supported variants that matches your target project.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/.vitepress/config.ts` around lines 225 - 232, The themeConfig currently
sets variant: "voidzero", which is unsupported by `@voidzero-dev/vitepress-theme`;
update the variant property inside themeConfig to one of the supported values
("vite", "viteplus", "vitest", "rolldown", or "oxc") that best matches your
project (e.g., change variant in themeConfig from "voidzero" to "vite" or
another supported option) so the theme renders and builds correctly.
…heme attribute selection in VitePress
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
docs/.vitepress/theme/index.ts (2)
272-276:hashchangelistener is never removed.The anonymous
hashchangelistener registered insideonMountedhas no pairedremoveEventListenerinonUnmounted(lines 278–285). Given the newonUnmountedcleanup block explicitly disposes the resize listener and theMutationObserver, this one is inconsistent and will leak if setup is ever re-run.♻️ Suggested change
+ const onHashChange = () => { + nextTick(handleTabHash); + }; - // Listen for hash changes - window.addEventListener("hashchange", () => { - nextTick(handleTabHash); - }); + window.addEventListener("hashchange", onHashChange); + // store on closure variable declared alongside onResize for cleanupAnd in
onUnmounted, add a matchingremoveEventListener("hashchange", onHashChange).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 272 - 276, The hashchange listener added inside onMounted uses an anonymous callback and is never removed; rename the callback to a stable function (e.g., onHashChange) that calls nextTick(handleTabHash), register window.addEventListener("hashchange", onHashChange) in onMounted, and remove it in onUnmounted via window.removeEventListener("hashchange", onHashChange) alongside the existing resize observer and MutationObserver cleanup.
152-175: Retry chains are not cancelable across route changes.
runSignInRelocationWithRetriesschedules up to 40 self-chainedsetTimeouts (~3s). It is called from initial mount, theMutationObservercallback,resize, and everyroute.pathchange. Each call starts a fresh independent chain with no token to cancel earlier ones, so rapid navigation can stack multiple overlapping retry chains. They're harmless once the link has thesign-in-relocatedclass (early return inmoveSignInToUtilityArea), but they keep running after unmount.Consider tracking the current attempt's timer id in a module-scoped variable and clearing it at the start of each new invocation (and in
onUnmounted) so retries are bounded to the latest call and don't outlive the component.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.vitepress/theme/index.ts` around lines 152 - 175, runSignInRelocationWithRetries starts many independent setTimeout chains that are never cancelled; to fix, add a module-scoped variable (e.g., currentSignInTimeoutId) and clearTimeout at the start of runSignInRelocationWithRetries before scheduling tryOnce, then have tryOnce assign each window.setTimeout return to that variable so the chain can be cleared; also clear that timeout in the component's onUnmounted handler to prevent retries after unmount; keep existing early return in moveSignInToUtilityArea and preserve signInRelocateWarned logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/.vitepress/theme/index.ts`:
- Around line 257-265: The interval and timeout created when headerObserver is
not yet set (window.setInterval in the initialization block) aren't cleared on
component unmount, and a later-attached MutationObserver may outlive the
component; update the logic so that the interval id and timeout id are stored in
local variables accessible to the onUnmounted handler and ensure onUnmounted
clears both the interval and timeout and also disconnects headerObserver if set;
reference the interval/timeout creation around tryAttachHeaderObserver and the
existing onUnmounted handler that disconnects headerObserver to add the cleanup.
In `@docs/index.md`:
- Line 35: The card titles in docs/index.md are using inconsistent casing (e.g.,
"Pages and Wiki" vs "Import and export"); pick one convention (suggest sentence
case) and update all six card title strings — including "Pages and Wiki" and
"Import and export" — to follow that convention consistently across the homepage
grid; search for the other card title literals in the file and replace them so
every card uses the same casing style.
---
Nitpick comments:
In `@docs/.vitepress/theme/index.ts`:
- Around line 272-276: The hashchange listener added inside onMounted uses an
anonymous callback and is never removed; rename the callback to a stable
function (e.g., onHashChange) that calls nextTick(handleTabHash), register
window.addEventListener("hashchange", onHashChange) in onMounted, and remove it
in onUnmounted via window.removeEventListener("hashchange", onHashChange)
alongside the existing resize observer and MutationObserver cleanup.
- Around line 152-175: runSignInRelocationWithRetries starts many independent
setTimeout chains that are never cancelled; to fix, add a module-scoped variable
(e.g., currentSignInTimeoutId) and clearTimeout at the start of
runSignInRelocationWithRetries before scheduling tryOnce, then have tryOnce
assign each window.setTimeout return to that variable so the chain can be
cleared; also clear that timeout in the component's onUnmounted handler to
prevent retries after unmount; keep existing early return in
moveSignInToUtilityArea and preserve signInRelocateWarned logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ae115bfd-b25c-4d79-9db9-4a337d4c4185
📒 Files selected for processing (3)
docs/.vitepress/config.tsdocs/.vitepress/theme/index.tsdocs/index.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/.vitepress/config.ts
| if (!headerObserver) { | ||
| const id = window.setInterval(() => { | ||
| tryAttachHeaderObserver(); | ||
| if (headerObserver) { | ||
| clearInterval(id); | ||
| } | ||
| }, 120); | ||
| window.setTimeout(() => clearInterval(id), 5000); | ||
| } |
There was a problem hiding this comment.
Interval and orphan observer not cleaned up on unmount.
The fallback setInterval (line 258) and its safety setTimeout (line 264) are only cleared via their own internal logic. If the component unmounts before headerObserver is attached and before the 5s safety timeout fires, the interval keeps polling tryAttachHeaderObserver(), and if it does eventually attach, the resulting MutationObserver will never be disconnected (the onUnmounted handler at lines 278–285 already ran). The safety setTimeout also outlives unmount.
Given onUnmounted was explicitly added to disconnect the observer, it's worth closing this gap for consistency.
♻️ Suggested change
let headerObserver: MutationObserver | null = null;
let onResize: (() => void) | null = null;
+ let attachIntervalId: ReturnType<typeof setInterval> | undefined;
+ let attachTimeoutId: ReturnType<typeof setTimeout> | undefined;
onMounted(() => {
@@
tryAttachHeaderObserver();
if (!headerObserver) {
- const id = window.setInterval(() => {
+ attachIntervalId = window.setInterval(() => {
tryAttachHeaderObserver();
if (headerObserver) {
- clearInterval(id);
+ clearInterval(attachIntervalId);
+ attachIntervalId = undefined;
}
}, 120);
- window.setTimeout(() => clearInterval(id), 5000);
+ attachTimeoutId = window.setTimeout(() => {
+ if (attachIntervalId) clearInterval(attachIntervalId);
+ attachIntervalId = undefined;
+ }, 5000);
}
@@
onUnmounted(() => {
headerObserver?.disconnect();
headerObserver = null;
+ if (attachIntervalId) clearInterval(attachIntervalId);
+ if (attachTimeoutId) clearTimeout(attachTimeoutId);
+ attachIntervalId = undefined;
+ attachTimeoutId = undefined;
if (onResize) {
window.removeEventListener("resize", onResize);
onResize = null;
}
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/.vitepress/theme/index.ts` around lines 257 - 265, The interval and
timeout created when headerObserver is not yet set (window.setInterval in the
initialization block) aren't cleared on component unmount, and a later-attached
MutationObserver may outlive the component; update the logic so that the
interval id and timeout id are stored in local variables accessible to the
onUnmounted handler and ensure onUnmounted clears both the interval and timeout
and also disconnects headerObserver if set; reference the interval/timeout
creation around tryAttachHeaderObserver and the existing onUnmounted handler
that disconnects headerObserver to add the cleanup.
| cta="Manage projects" | ||
| /> | ||
| <Card | ||
| title="Pages and Wiki" |
There was a problem hiding this comment.
Inconsistent casing across card titles.
"Pages and Wiki" capitalizes "Wiki" while "Import and export" uses lowercase "and export". Pick one convention (sentence case or title case) and apply it uniformly to all six cards so the homepage grid reads consistently.
🪄 Proposed fix (sentence case)
- title="Pages and Wiki"
+ title="Pages and wiki"Also applies to: 49-49
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/index.md` at line 35, The card titles in docs/index.md are using
inconsistent casing (e.g., "Pages and Wiki" vs "Import and export"); pick one
convention (suggest sentence case) and update all six card title strings —
including "Pages and Wiki" and "Import and export" — to follow that convention
consistently across the homepage grid; search for the other card title literals
in the file and replace them so every card uses the same casing style.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/.vitepress/theme/components/Card.vue`:
- Around line 63-68: The anchor rendered by Card.vue uses hasDestination and
resolvedHref but doesn't set target/rel for external URLs; update the component
rendering logic (using hasDestination, resolvedHref and cta) to detect external
links (e.g., resolvedHref startsWith('http') or origin !== current origin) and,
only for external destinations, set target="_blank" and rel="noopener
noreferrer" on the <a> element while leaving internal links unchanged.
In `@docs/.vitepress/theme/style.css`:
- Line 1: The CSS file docs/.vitepress/theme/style.css fails the formatting CI;
run the formatter and commit the changes by executing the project formatting
commands (run pnpm fix:format), then verify the check passes with pnpm
check:format; if the formatter updates docs/.vitepress/theme/style.css, stage
and commit that file so CI no longer reports the formatting error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 974bbd82-868b-40ff-b1df-a3b8ad26531e
📒 Files selected for processing (3)
docs/.vitepress/theme/components/Card.vuedocs/.vitepress/theme/index.tsdocs/.vitepress/theme/style.css
| <component | ||
| :is="hasDestination ? 'a' : 'div'" | ||
| :href="hasDestination ? resolvedHref : undefined" | ||
| class="card-link" | ||
| :class="{ 'card-link--with-cta': cta }" | ||
| > |
There was a problem hiding this comment.
External links open in same tab without rel="noopener".
The homepage uses link="https://developers.plane.so/self-hosting/overview" which is external. The rendered <a> lacks target and rel attributes, so external destinations open in the same tab (losing docs context) and miss rel="noopener noreferrer" hardening.
♻️ Proposed refinement
+<script setup>
+// ...existing code...
+const isExternal = computed(() => /^https?:\/\//i.test(resolvedHref.value));
+</script>
<template>
<component
:is="hasDestination ? 'a' : 'div'"
:href="hasDestination ? resolvedHref : undefined"
+ :target="hasDestination && isExternal ? '_blank' : undefined"
+ :rel="hasDestination && isExternal ? 'noopener noreferrer' : undefined"
class="card-link"
:class="{ 'card-link--with-cta': cta }"
>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/.vitepress/theme/components/Card.vue` around lines 63 - 68, The anchor
rendered by Card.vue uses hasDestination and resolvedHref but doesn't set
target/rel for external URLs; update the component rendering logic (using
hasDestination, resolvedHref and cta) to detect external links (e.g.,
resolvedHref startsWith('http') or origin !== current origin) and, only for
external destinations, set target="_blank" and rel="noopener noreferrer" on the
<a> element while leaving internal links unchanged.
| VitePress's unlayered default theme styles. */ | ||
| @import "tailwindcss/theme" layer(theme); | ||
| @import "tailwindcss/utilities" layer(utilities); | ||
| /** @format */ |
There was a problem hiding this comment.
Formatting CI is failing on this file.
oxfmt --check reports a formatting issue for docs/.vitepress/theme/style.css. Please run pnpm fix:format and confirm pnpm check:format passes.
As per coding guidelines, "Run pnpm fix:format before committing and ensure CI check via pnpm check:format passes; formatting is enforced".
🧰 Tools
🪛 GitHub Actions: CI
[error] 1-1: oxfmt --check failed: formatting issues found. Format issues found in above 1 files. Run without '--check' to fix.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/.vitepress/theme/style.css` at line 1, The CSS file
docs/.vitepress/theme/style.css fails the formatting CI; run the formatter and
commit the changes by executing the project formatting commands (run pnpm
fix:format), then verify the check passes with pnpm check:format; if the
formatter updates docs/.vitepress/theme/style.css, stage and commit that file so
CI no longer reports the formatting error.
Summary by CodeRabbit
New Features
Style
Chores