diff --git a/docs/.vitepress/theme/components/CardGroup.vue b/docs/.vitepress/theme/components/CardGroup.vue
index 260208e..335095f 100644
--- a/docs/.vitepress/theme/components/CardGroup.vue
+++ b/docs/.vitepress/theme/components/CardGroup.vue
@@ -1,19 +1,28 @@
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index bf8d8c0..42de683 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,14 +1,31 @@
-import DefaultTheme from "vitepress/theme";
import type { Theme } from "vitepress";
-import { onMounted, watch, nextTick } from "vue";
-import { useRoute } from "vitepress";
+import type { ThemeContext } from "@voidzero-dev/vitepress-theme";
+import VoidZeroTheme from "@voidzero-dev/vitepress-theme";
+import { themeContextKey } from "@voidzero-dev/vitepress-theme";
+import { onMounted, onUnmounted, watch, nextTick } from "vue";
+import { useData, useRoute } from "vitepress";
import { enhanceAppWithTabs } from "vitepress-plugin-tabs/client";
import mediumZoom from "medium-zoom";
import Card from "./components/Card.vue";
import CardGroup from "./components/CardGroup.vue";
import Tags from "./components/Tags.vue";
+import Layout from "./Layout.vue";
import "./style.css";
+/**
+ * OSSHeader (used on doc pages) injects this context for the bar logo — *not* `themeConfig.logo`.
+ * The `viteplus` entry in the package overwrites it with "Vite+" assets; we use the base
+ * `VoidZeroTheme` and provide Plane branding here.
+ */
+const planeThemeContext: ThemeContext = {
+ /* OSSHeader renders logoDark in light mode and logoLight in dark mode. */
+ logoDark: "https://media.docs.plane.so/logo/new-logo-white.png",
+ logoLight: "https://media.docs.plane.so/logo/new-logo-dark.png",
+ logoAlt: "Plane",
+ footerBg: "https://media.docs.plane.so/logo/og-docs.webp",
+ monoIcon: "https://media.docs.plane.so/logo/favicon-32x32.png",
+};
+
/**
* Handles tab activation based on URL hash
*/
@@ -73,36 +90,213 @@ function updateHashOnTabClick(event: Event) {
}
}
+/**
+ * Move "Sign in" CTA to the right utility area (between search and theme toggle).
+ * The upstream OSSHeader renders nav links on the left; we remap only this CTA.
+ * Returns true if the link was found and moved (or already in the target region).
+ */
+function moveSignInToUtilityArea(): boolean {
+ if (typeof document === "undefined") return false;
+
+ const signInLink = document.querySelector(
+ '.docs-layout header a.VPLink[href*="sign-in"]',
+ ) as HTMLAnchorElement | null;
+ if (!signInLink) return false;
+
+ // Keep the CTA hidden while we're still deciding where it belongs.
+ signInLink.classList.add("sign-in-relocating");
+
+ const markRelocated = () => {
+ signInLink.classList.add("sign-in-relocated");
+ signInLink.classList.remove("sign-in-relocating");
+ };
+
+ if (signInLink.classList.contains("sign-in-relocated")) {
+ signInLink.classList.remove("sign-in-relocating");
+ return true;
+ }
+
+ const appearanceToggle = document.querySelector(
+ ".docs-layout header .VPNavBarAppearance",
+ ) as HTMLElement | null;
+
+ if (
+ appearanceToggle?.parentElement &&
+ signInLink.parentElement === appearanceToggle.parentElement
+ ) {
+ markRelocated();
+ return true;
+ }
+
+ const extraMenu = document.querySelector(
+ ".docs-layout header .VPNavBarExtra",
+ ) as HTMLElement | null;
+ if (extraMenu?.parentElement && signInLink.parentElement === extraMenu.parentElement) {
+ markRelocated();
+ return true;
+ }
+
+ // Desktop (xl+): insert before theme toggle inside the utilities row.
+ if (
+ appearanceToggle?.parentElement &&
+ signInLink.parentElement !== appearanceToggle.parentElement
+ ) {
+ appearanceToggle.parentElement.insertBefore(signInLink, appearanceToggle);
+ markRelocated();
+ return true;
+ }
+
+ // Tablet fallback (lg-xl): keep it in right controls row before the extra menu.
+ if (extraMenu?.parentElement && signInLink.parentElement !== extraMenu.parentElement) {
+ extraMenu.parentElement.insertBefore(signInLink, extraMenu);
+ markRelocated();
+ return true;
+ }
+
+ return false;
+}
+
+let signInRelocateWarned = false;
+
+function runSignInRelocationWithRetries() {
+ if (typeof document === "undefined" || !document.querySelector(".docs-layout")) {
+ return;
+ }
+
+ const tryOnce = (attempt: number) => {
+ if (moveSignInToUtilityArea()) {
+ return;
+ }
+ if (attempt < 40) {
+ window.setTimeout(() => tryOnce(attempt + 1), 75);
+ } else if (!signInRelocateWarned) {
+ const link = document.querySelector('.docs-layout header a.VPLink[href*="sign-in"]');
+ if (link) {
+ link.classList.remove("sign-in-relocating");
+ }
+ if (link && !link.classList.contains("sign-in-relocated")) {
+ signInRelocateWarned = true;
+ console.warn(
+ "[plane-docs] Sign-in could not be relocated (header not ready or selectors changed).",
+ );
+ }
+ }
+ };
+
+ tryOnce(0);
+}
+
+function debounce(fn: () => void, ms: number) {
+ let t: ReturnType | undefined;
+ return () => {
+ if (t) clearTimeout(t);
+ t = setTimeout(fn, ms);
+ };
+}
+
export default {
- extends: DefaultTheme,
- enhanceApp({ app }) {
- enhanceAppWithTabs(app);
- app.component("Card", Card);
- app.component("CardGroup", CardGroup);
- app.component("Tags", Tags);
+ extends: VoidZeroTheme,
+ Layout,
+ enhanceApp(ctx) {
+ VoidZeroTheme.enhanceApp?.(ctx);
+ ctx.app.provide(themeContextKey, planeThemeContext);
+ enhanceAppWithTabs(ctx.app);
+ ctx.app.component("Card", Card);
+ ctx.app.component("CardGroup", CardGroup);
+ ctx.app.component("Tags", Tags);
},
setup() {
if (typeof window === "undefined") return;
const route = useRoute();
+ const { isDark } = useData();
+
+ /**
+ * VitePress’s inline "check-dark-mode" script only adds the `dark` class and
+ * never removes it, so a stale `class="dark"` on (e.g. after SSG) can
+ * persist in light mode. That leaves `.dark …` global/navbar rules applied
+ * until a theme toggle re-syncs. Keep `documentElement` in lockstep with
+ * `isDark` (same source of truth as the toggle).
+ */
+ const syncOssHeaderThemeAttr = (dark: boolean) => {
+ const header = document.querySelector(".docs-layout header") as HTMLElement | null;
+ const bar = header?.parentElement;
+ if (!bar) return;
+ if (dark) bar.setAttribute("data-theme", "dark");
+ else bar.removeAttribute("data-theme");
+ };
+
+ watch(
+ isDark,
+ (dark) => {
+ document.documentElement.classList.toggle("dark", dark);
+ syncOssHeaderThemeAttr(dark);
+ },
+ { immediate: true },
+ );
+
+ onMounted(() => {
+ syncOssHeaderThemeAttr(isDark.value);
+ });
const zoom = mediumZoom(".vp-doc img", {
background: "rgba(0, 0, 0, 0.8)",
});
+ let headerObserver: MutationObserver | null = null;
+ let onResize: (() => void) | null = null;
+
onMounted(() => {
+ runSignInRelocationWithRetries();
+
// Delay tab hash handling to ensure tabs are rendered
setTimeout(() => {
handleTabHash();
setupTabHashUpdates();
}, 100);
+ 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();
+ if (!headerObserver) {
+ const id = window.setInterval(() => {
+ tryAttachHeaderObserver();
+ if (headerObserver) {
+ clearInterval(id);
+ }
+ }, 120);
+ window.setTimeout(() => clearInterval(id), 5000);
+ }
+
+ onResize = debounce(() => {
+ runSignInRelocationWithRetries();
+ }, 150);
+ window.addEventListener("resize", onResize);
+
// Listen for hash changes
window.addEventListener("hashchange", () => {
nextTick(handleTabHash);
});
});
+ onUnmounted(() => {
+ headerObserver?.disconnect();
+ headerObserver = null;
+ if (onResize) {
+ window.removeEventListener("resize", onResize);
+ onResize = null;
+ }
+ });
+
// Watch for route changes
watch(
() => route.path,
@@ -112,6 +306,7 @@ export default {
zoom.attach(":not(a) > img:not(.VPImage)");
handleTabHash();
setupTabHashUpdates();
+ runSignInRelocationWithRetries();
});
},
);
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
index f8b1166..aa57946 100644
--- a/docs/.vitepress/theme/style.css
+++ b/docs/.vitepress/theme/style.css
@@ -1,13 +1,6 @@
-/* ================================================
- TAILWIND CSS
- ================================================ */
-/* Import Tailwind theme + utilities WITHOUT Preflight (CSS reset).
- VitePress has its own base/reset styles for markdown rendering,
- so Tailwind's Preflight conflicts with headings, links, lists, etc.
- The layer() wrappers ensure Tailwind utilities don't override
- VitePress's unlayered default theme styles. */
-@import "tailwindcss/theme" layer(theme);
-@import "tailwindcss/utilities" layer(utilities);
+/** @format */
+
+@import "@voidzero-dev/vitepress-theme/src/styles/index.css";
@source "../../**/*.{vue,md}";
@source "../**/*.{vue,md}";
@@ -155,11 +148,11 @@
/* Fonts */
--vp-font-family-base:
- "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
- sans-serif;
+ "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
+ Ubuntu, Cantarell, sans-serif;
--vp-font-family-mono:
- "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas,
- "Liberation Mono", "Courier New", monospace;
+ "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco,
+ Consolas, "Liberation Mono", "Courier New", monospace;
}
/* Font rendering optimizations */
@@ -442,7 +435,8 @@ div[class*="language-"] > span.lang {
UTILITY CLASSES
================================================ */
-.hidden {
+/* Keep markdown helper class, but don't override Tailwind's global `.hidden` utility. */
+.vp-doc .hidden {
display: none !important;
}
@@ -564,7 +558,7 @@ div[class*="language-"] > span.lang {
}
.dark .card-link:hover .card-title {
- color: #0088cc !important;
+ color: #f9fafb !important;
}
.card-description {
@@ -578,109 +572,406 @@ div[class*="language-"] > span.lang {
color: #9ca3af;
}
-/* ================================================
- SEARCH BOX WIDTH IN HEADER
- ================================================ */
+/* Home (index) feature cards — light panels, CTA line, 3×2 grid (see design ref) */
+.vp-doc .home-feature-cards {
+ gap: 22px;
+ margin: 32px 0 40px;
+}
-/* Make the header search box wider */
-.DocSearch.DocSearch-Button {
- justify-content: space-between !important;
+.vp-doc .home-feature-cards .card-link {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ height: 100%;
+ padding: 28px;
+ border-radius: 14px;
+ border: none;
+ background: #f7f8f9;
+ box-shadow: none;
+ transition: background 0.2s ease;
}
-@media (min-width: 768px) {
- .DocSearch.DocSearch-Button {
- width: 280px !important;
- }
+.vp-doc .home-feature-cards .card-link:hover {
+ border: none;
+ box-shadow: none;
+ background: #eef0f2;
}
-@media (min-width: 1024px) {
- .DocSearch.DocSearch-Button {
- width: 400px !important;
- }
+.vp-doc .home-feature-cards .card-link:hover .card-title {
+ color: #111827 !important;
}
-@media (min-width: 1280px) {
- .DocSearch.DocSearch-Button {
- width: 500px !important;
- }
+.vp-doc .home-feature-cards .card-icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin: 0 0 10px 0;
+ color: #0a0a0a;
+}
+
+.vp-doc .home-feature-cards .card-icon svg {
+ width: 24px;
+ height: 24px;
+ flex-shrink: 0;
+}
+
+.vp-doc .home-feature-cards .card-title {
+ font-size: 1.125rem !important;
+ line-height: 1.3 !important;
+ font-weight: 600 !important;
+ color: #0a0a0a !important;
+ margin: 0 0 8px 0 !important;
+}
+
+.vp-doc .home-feature-cards .card-description {
+ font-size: 15px;
+ line-height: 1.6;
+ color: #4a5568;
+ margin: 0 0 0 0;
+ flex: 1 1 auto;
+}
+
+.vp-doc .home-feature-cards .card-cta {
+ display: block;
+ margin-top: 20px;
+ padding-top: 0;
+ font-size: 15px;
+ font-weight: 500;
+ color: #0066cc;
+ line-height: 1.3;
+}
+
+.vp-doc .home-feature-cards .card-link:hover .card-cta {
+ color: #0052a3;
+}
+
+.vp-doc .home-feature-cards .card-link--with-cta .card-cta {
+ margin-top: auto;
+ padding-top: 16px;
+}
+
+.dark .vp-doc .home-feature-cards .card-link {
+ background: #141414;
+}
+
+.dark .vp-doc .home-feature-cards .card-link:hover {
+ background: #1a1a1a;
+}
+
+.dark .vp-doc .home-feature-cards .card-icon {
+ color: #f4f4f4;
+}
+
+.dark .vp-doc .home-feature-cards .card-title {
+ color: #f4f4f4 !important;
+}
+
+.dark .vp-doc .home-feature-cards .card-link:hover .card-title {
+ color: #f4f4f4 !important;
+}
+
+.dark .vp-doc .home-feature-cards .card-description {
+ color: #a1a1aa;
+}
+
+.dark .vp-doc .home-feature-cards .card-cta {
+ color: #2f9bd5;
+}
+
+.dark .vp-doc .home-feature-cards .card-link:hover .card-cta {
+ color: #5eb5e0;
}
/* ================================================
- SIGN IN BUTTON STYLING
+ NAVBAR — PLANE (logo + title, search, sign-in, theme, socials)
+ Doc pages use OSSHeader (not VitePress default .VPNavBar), but reuse VPNav* pieces.
================================================ */
-/* Target the Sign in link using multiple class combinations */
-.VPNavBarMenuLink.VPLink[href*="sign-in"],
-a.VPLink.VPNavBarMenuLink[href*="sign-in"],
-.VPNavBarMenu .VPLink[href="https://app.plane.so/sign-in"] {
+/* Remove any inherited top layout offset beneath fixed header */
+.docs-layout {
+ --vp-layout-top-height: 0px !important;
+}
+
+.docs-layout header.wrapper {
+ border-bottom-color: #ececec !important;
+}
+
+.dark .docs-layout header.wrapper {
+ border-bottom-color: #2a2a2a !important;
+}
+
+/* OSSHeader stacks the logo link as flex-col; we want mark + "Plane" in a row. */
+.docs-layout header a[href="/"].flex.flex-col {
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.docs-layout header a[href="/"] img.h-4 {
+ height: 1.5rem;
+ width: auto;
+}
+
+/* Search control sizing/shape */
+@media (min-width: 768px) {
+ .docs-layout header .VPNavBarSearchButton {
+ min-width: min(300px, 28vw);
+ height: 36px;
+ padding: 0 12px;
+ justify-content: space-between;
+ border-radius: 12px !important;
+ border: 1px solid #e7e7e7;
+ background: #fff;
+ }
+
+ .dark .docs-layout header .VPNavBarSearchButton {
+ border-color: #323232;
+ background: #111;
+ }
+}
+
+/* Sign in — primary CTA (works before/after relocation) */
+.docs-layout header a.VPLink[href*="sign-in"] {
background: #006399 !important;
color: #ffffff !important;
- padding: 6px 14px !important;
- margin-top: 15px !important;
- margin-bottom: 15px !important;
+ padding: 5px 14px !important;
border-radius: 6px !important;
font-weight: 500 !important;
font-size: 14px !important;
- transition: all 0.2s ease !important;
- line-height: normal !important;
+ line-height: 1.25 !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
- vertical-align: middle !important;
+ margin: 0 !important;
+ opacity: 1 !important;
+ transition:
+ background 0.2s ease,
+ box-shadow 0.2s ease,
+ transform 0.2s ease !important;
}
-/* Hide the external link arrow icon */
-.VPNavBarMenuLink.VPLink[href*="sign-in"] .vp-external-link-icon,
-a.VPLink.VPNavBarMenuLink[href*="sign-in"] .vp-external-link-icon,
-.VPNavBarMenu .VPLink[href="https://app.plane.so/sign-in"] .vp-external-link-icon,
-.VPNavBarMenuLink.VPLink[href*="sign-in"] svg,
-a.VPLink.VPNavBarMenuLink[href*="sign-in"] svg,
-.VPNavBarMenuLink.VPLink[href*="sign-in"] .VPImage,
-a.VPLink.VPNavBarMenuLink[href*="sign-in"] .VPImage {
- display: none !important;
+.docs-layout header a.VPLink[href*="sign-in"].sign-in-relocating {
+ visibility: hidden !important;
}
-/* Also hide the icon using ::after if it's a pseudo-element */
-.VPNavBarMenuLink.VPLink[href*="sign-in"]::after,
-a.VPLink.VPNavBarMenuLink[href*="sign-in"]::after {
+.docs-layout header a.VPLink[href*="sign-in"] .vp-external-link-icon {
display: none !important;
- content: none !important;
}
-.VPNavBarMenuLink.VPLink[href*="sign-in"]:hover,
-a.VPLink.VPNavBarMenuLink[href*="sign-in"]:hover {
+.docs-layout header a.VPLink[href*="sign-in"]:hover {
background: #0078b8 !important;
- transform: translateY(-1px) !important;
box-shadow: 0 2px 8px rgba(0, 99, 153, 0.25) !important;
- text-decoration: none !important;
+ transform: translateY(-1px);
+ opacity: 1 !important;
}
-/* Dark mode variant */
-.dark .VPNavBarMenuLink.VPLink[href*="sign-in"],
-.dark a.VPLink.VPNavBarMenuLink[href*="sign-in"] {
- background: #006399 !important;
- color: #ffffff !important;
+/* Keep nav utility controls aligned like the reference */
+.docs-layout header .VPNavBarAppearance {
+ border-left: 1px solid #ececec;
+ padding-left: 10px;
+ margin-left: 2px;
}
-.dark .VPNavBarMenuLink.VPLink[href*="sign-in"]:hover,
-.dark a.VPLink.VPNavBarMenuLink[href*="sign-in"]:hover {
- background: #0078b8 !important;
- box-shadow: 0 2px 8px rgba(0, 99, 153, 0.35) !important;
+.docs-layout header .VPNavBarSocialLinks {
+ border-left: 1px solid #ececec;
+ padding-left: 10px;
}
-/* ================================================
- CENTER SEARCH BAR IN NAVBAR
- ================================================ */
+.dark .docs-layout header .VPNavBarAppearance,
+.dark .docs-layout header .VPNavBarSocialLinks {
+ border-left-color: #2a2a2a;
+}
-@media (min-width: 768px) {
- .VPNavBarSearch {
- position: fixed !important;
- left: 50% !important;
- transform: translateX(-50%) !important;
- top: 0 !important;
- height: var(--vp-nav-height) !important;
- display: flex !important;
- align-items: center !important;
+/* Remove extra desktop strip between navbar and docs content/sidebar. */
+@media (min-width: 1024px) {
+ /* Extend side borders upward so they visually touch the navbar edge. */
+ .docs-layout .content-wrapper::before,
+ .docs-layout .content-wrapper::after {
+ content: "";
+ position: absolute;
+ top: -5px;
+ width: 1px;
+ height: 5px;
+ background: var(--color-stroke);
+ pointer-events: none;
+ }
+
+ .docs-layout .content-wrapper::before {
+ left: 0;
+ }
+
+ .docs-layout .content-wrapper::after {
+ right: 0;
+ }
+
+ .dark .docs-layout .content-wrapper::before,
+ .dark .docs-layout .content-wrapper::after {
+ background: var(--color-nickel);
+ }
+
+ .docs-layout .VPLocalNav.has-sidebar {
+ display: none !important;
+ }
+
+ /* Do not zero VPContent padding-top: the OSS header is `lg:fixed`, and the theme
+ uses padding-top: var(--vp-nav-height) here so the main column starts below the bar.
+ Removing it makes the center doc sit under the navbar while sidebars are sticky. */
+}
+
+/* 3-column doc: left nav + right outline share one baseline; body copy sits slightly lower */
+@media (min-width: 1280px) {
+ .docs-layout .VPSidebar .nav {
+ /* .VPSidebar already has top padding; extra padding here was stacking—omit so nav links sit 20px higher. */
+ padding-top: 0 !important;
+ }
+
+ /* Theme VPDoc uses `padding: 0 32px` which adds 32px to the *right* of the whole
+ three-column row — empty gap past the "On this page" rail. */
+ .docs-layout .VPDoc.has-aside {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+ }
+
+ .docs-layout .VPDoc.has-aside .aside .aside-container {
+ padding-top: 20px !important;
+ }
+
+ .docs-layout .VPDoc.has-aside .content {
+ /* Theme default adds large top padding here; remove it so only `.main` controls vertical offset */
+ padding: 0 32px 128px !important;
+ }
+
+ .docs-layout .VPDoc.has-aside .content .main {
+ /* Slightly lower than both side columns */
+ padding-top: 32px !important;
+ }
+
+ /* Right "On this page" rail: the theme’s `.aside-container` is 224px, so the flex
+ item `.aside` often shrink-wraps to ~224px. That makes inner `width: 100%` only
+ fill 224 of the 256px max. Lock the rail to the design width so outline uses it. */
+ .docs-layout .VPDoc.has-aside .aside {
+ flex: 0 0 256px;
+ width: 256px;
+ max-width: 256px;
+ min-width: 0;
+ padding-left: 0;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDoc.has-aside .aside .aside-container {
+ display: block;
+ width: 100% !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ padding-left: 4px;
+ padding-right: 4px;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDoc.has-aside .aside .aside-content {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDoc.has-aside .aside .VPDocAside {
+ width: 100%;
+ min-width: 0;
+ }
+
+ .docs-layout .VPDoc.has-aside .VPDocAsideOutline,
+ .docs-layout .VPDoc.has-aside .VPDocAsideOutline .content {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDoc.has-aside .VPDocAsideOutline .outline-title {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDocAsideOutline .VPDocOutlineItem {
+ width: 100%;
+ list-style: none;
+ box-sizing: border-box;
+ }
+
+ .docs-layout .VPDocAsideOutline .VPDocOutlineItem.root {
+ margin: 0;
+ padding: 0;
+ }
+
+ .docs-layout .VPDocAsideOutline .VPDocOutlineItem li {
+ width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ }
+
+ /* VPDocOutlineItem uses nowrap + ellipsis; allow long headings to wrap. */
+ .docs-layout .VPDocAsideOutline a.outline-link {
+ white-space: normal;
+ overflow: visible;
+ text-overflow: unset;
+ line-height: 1.45;
+ padding: 2px 0 4px;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ /* Air between sibling items in the right-rail outline. */
+ .docs-layout .VPDocAsideOutline .VPDocOutlineItem li + li {
+ margin-top: 6px;
+ }
+
+ .docs-layout
+ .VPDocAsideOutline
+ .VPDocOutlineItem
+ li
+ > .VPDocOutlineItem.nested {
+ margin-top: 4px;
+ }
+
+ .docs-layout .VPDocOutlineItem.nested {
+ padding-left: 4px;
+ padding-right: 4px;
+ }
+}
+
+/* Right "On this page" column: theme keeps has-aside when outline is enabled, but
+ the outline is display:none until .has-outline. Hide the whole column and let
+ the main column use that space (unless Carbon ads fill the rail). */
+@media (min-width: 1280px) {
+ .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;
}
}
diff --git a/docs/.vitepress/types/modules.d.ts b/docs/.vitepress/types/modules.d.ts
new file mode 100644
index 0000000..3284324
--- /dev/null
+++ b/docs/.vitepress/types/modules.d.ts
@@ -0,0 +1,10 @@
+declare module "*.vue" {
+ import type { DefineComponent } from "vue";
+
+ const component: DefineComponent, Record, unknown>;
+ export default component;
+}
+
+declare module "vitepress-plugin-tabs/client" {
+ export function enhanceAppWithTabs(app: unknown): void;
+}
diff --git a/docs/index.md b/docs/index.md
index 104faec..4ed41c8 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,67 +1,62 @@
---
-layout: home
+layout: doc
title: Plane Docs
description: Everything you need to learn Plane, manage projects, and build powerful workflows.
+---
-hero:
- name: "Plane Documentation"
- text: "Plan, track, and ship your work"
- tagline: Guides and reference for using Plane to manage projects, collaborate with teams, and automate workflows.
- actions:
- - theme: brand
- text: Get Started
- link: /introduction/home
- - theme: alt
- text: Tutorials
- link: /introduction/tutorials/overview
+
-features:
- - icon:
- src: /icons/building-2.svg
- width: 25
- height: 25
- title: Workspaces
- details: Set up workspaces, invite members, configure roles and permissions, and collaborate effectively.
- link: /core-concepts/workspaces/overview
- linkText: Configure workspaces
- - icon:
- src: /icons/kanban.svg
- width: 25
- height: 25
- title: Projects
- details: Create projects, manage work items, plan with cycles and modules, and track progress.
- link: /core-concepts/projects/overview
- linkText: Manage projects
- - icon:
- src: /icons/book-open.svg
- width: 25
- height: 25
- title: Pages and Wiki
- details: Create pages, build wikis, and keep your team's knowledge organized and accessible.
- link: /core-concepts/pages/overview
- linkText: Explore pages
- - icon:
- src: /icons/plug.svg
- width: 25
- height: 25
- title: Integrations
- details: Connect Plane with GitHub, GitLab, Slack, Sentry, and more to streamline your workflow.
- link: /integrations/about
- linkText: View integrations
- - icon:
- src: /icons/arrow-left-right.svg
- width: 25
- height: 25
- title: Import and export
- details: Migrate from Jira, Asana, Linear, ClickUp, and other tools with our importers.
- link: /importers/overview
- linkText: Import data
- - icon:
- src: /icons/terminal.svg
- width: 25
- height: 25
- title: Developers
- details: Self-host Plane, use the API, and extend Plane with MCP servers, agents, and custom integrations.
- link: https://developers.plane.so/self-hosting/overview
- linkText: Developer docs
----
+# Plane documentation
+
+Plan, track, and ship your work with Plane.
+
+This documentation helps you learn Plane, manage projects, collaborate with
+teams, and automate workflows.
+
+[Get Started](/introduction/home) ·
+[Tutorials](/introduction/tutorials/overview)
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index 5f6f6cf..da4a8c6 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
},
"dependencies": {
"@tailwindcss/vite": "^4.2.1",
+ "@voidzero-dev/vitepress-theme": "^4.8.4",
"lucide-vue-next": "^0.577.0",
"medium-zoom": "^1.1.0",
"tailwindcss": "^4.2.1",
@@ -27,6 +28,7 @@
"vue": "^3.5.13"
},
"devDependencies": {
+ "@types/node": "^25.6.0",
"oxfmt": "^0.36.0"
},
"engines": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 45e59f1..64b657b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,7 +14,10 @@ importers:
dependencies:
'@tailwindcss/vite':
specifier: ^4.2.1
- version: 4.2.1(vite@6.4.2(jiti@2.6.1)(lightningcss@1.31.1))
+ version: 4.2.1(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))
+ '@voidzero-dev/vitepress-theme':
+ specifier: ^4.8.4
+ version: 4.8.4(focus-trap@7.8.0)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))(vitepress@1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30)
lucide-vue-next:
specifier: ^0.577.0
version: 0.577.0(vue@3.5.30)
@@ -26,14 +29,17 @@ importers:
version: 4.2.1
vitepress:
specifier: ^1.6.3
- version: 1.6.4(@algolia/client-search@5.49.2)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3)
+ version: 1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3)
vitepress-plugin-tabs:
specifier: ^0.8.0
- version: 0.8.0(vitepress@1.6.4(@algolia/client-search@5.49.2)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30)
+ version: 0.8.0(vitepress@1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30)
vue:
specifier: ^3.5.13
version: 3.5.30
devDependencies:
+ '@types/node':
+ specifier: ^25.6.0
+ version: 25.6.0
oxfmt:
specifier: ^0.36.0
version: 0.36.0
@@ -136,9 +142,15 @@ packages:
'@docsearch/css@3.8.2':
resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==}
+ '@docsearch/css@4.6.2':
+ resolution: {integrity: sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==}
+
'@docsearch/js@3.8.2':
resolution: {integrity: sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==}
+ '@docsearch/js@4.6.2':
+ resolution: {integrity: sha512-qj1yoxl3y4GKoK7+VM6fq/rQqPnvUmg3IKzJ9x0VzN14QVzdB/SG/J6VfV1BWT5RcPUFxIcVwoY1fwHM2fSRRw==}
+
'@docsearch/react@3.8.2':
resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==}
peerDependencies:
@@ -156,6 +168,9 @@ packages:
search-insights:
optional: true
+ '@docsearch/sidepanel-js@4.6.2':
+ resolution: {integrity: sha512-Pni85AP/GwRj7fFg8cBJp0U04tzbueBvWSd3gysgnOsVnQVSZwSYncfErUScLE1CAtR+qocPDFjmYR9AMRNJtQ==}
+
'@esbuild/aix-ppc64@0.25.12':
resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
engines: {node: '>=18'}
@@ -312,12 +327,35 @@ packages:
cpu: [x64]
os: [win32]
+ '@floating-ui/core@1.7.5':
+ resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
+ '@floating-ui/dom@1.7.6':
+ resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+ '@floating-ui/utils@0.2.11':
+ resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
+ '@floating-ui/vue@1.1.11':
+ resolution: {integrity: sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==}
+
'@iconify-json/simple-icons@1.2.73':
resolution: {integrity: sha512-nQZTwul4c2zBqH/aLP4zMOiElj93T6HawbrP+sFQKpxmBdS5x1duCK3cAnkj6dntHz84EYkzaQRM83V2pj4qxA==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
+ '@iconify/vue@5.0.0':
+ resolution: {integrity: sha512-C+KuEWIF5nSBrobFJhT//JS87OZ++QDORB6f2q2Wm6fl2mueSTpFBeBsveK0KW9hWiZ4mNiPjsh6Zs4jjdROSg==}
+ peerDependencies:
+ vue: '>=3'
+
+ '@internationalized/date@3.12.1':
+ resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==}
+
+ '@internationalized/number@3.6.6':
+ resolution: {integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==}
+
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -456,6 +494,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@rive-app/canvas-lite@2.37.3':
+ resolution: {integrity: sha512-lw4M13Yu1VZSlys/4yW3O4IGMXqSsZCdwPTEkspR9PkphJW+WWjxT99F946eXAVV6aNg5gE3XuW3PQaoDciiYg==}
+
'@rollup/rollup-android-arm-eabi@4.59.0':
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
cpu: [arm]
@@ -618,6 +659,9 @@ packages:
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+ '@swc/helpers@0.5.21':
+ resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==}
+
'@tailwindcss/node@4.2.1':
resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==}
@@ -707,11 +751,24 @@ packages:
resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==}
engines: {node: '>= 20'}
+ '@tailwindcss/typography@0.5.19':
+ resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1'
+
'@tailwindcss/vite@4.2.1':
resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==}
peerDependencies:
vite: 6.4.2
+ '@tanstack/virtual-core@3.14.0':
+ resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==}
+
+ '@tanstack/vue-virtual@3.13.24':
+ resolution: {integrity: sha512-A0k2qF0zFSUStXSZkGXABouXr2Tw2Ztl/cVIYG9qy84uR8W7UNjAcX3DvzBS3YnDcwvLxab8v7dbmYBZ39itDA==}
+ peerDependencies:
+ vue: ^2.7.0 || ^3.0.0
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -730,6 +787,9 @@ packages:
'@types/mdurl@2.0.0':
resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+ '@types/node@25.6.0':
+ resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
+
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -746,6 +806,12 @@ packages:
vite: 6.4.2
vue: ^3.2.25
+ '@voidzero-dev/vitepress-theme@4.8.4':
+ resolution: {integrity: sha512-o7R2g9OPu5iLW3R4PNkj+JRSIncO8f1hTY0YvO13OMP/kytKUn8MLhy4kTvx+IMRSLeqrGfrBpze/OEPKzcLUQ==}
+ peerDependencies:
+ vitepress: ^2.0.0-alpha.16
+ vue: ^3.5.0
+
'@vue/compiler-core@3.5.30':
resolution: {integrity: sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==}
@@ -787,6 +853,11 @@ packages:
'@vueuse/core@12.8.2':
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
+ '@vueuse/core@14.2.1':
+ resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==}
+ peerDependencies:
+ vue: ^3.5.0
+
'@vueuse/integrations@12.8.2':
resolution: {integrity: sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==}
peerDependencies:
@@ -828,16 +899,70 @@ packages:
universal-cookie:
optional: true
+ '@vueuse/integrations@14.2.1':
+ resolution: {integrity: sha512-2LIUpBi/67PoXJGqSDQUF0pgQWpNHh7beiA+KG2AbybcNm+pTGWT6oPGlBgUoDWmYwfeQqM/uzOHqcILpKL7nA==}
+ peerDependencies:
+ async-validator: ^4
+ axios: ^1
+ change-case: ^5
+ drauu: ^0.4
+ focus-trap: ^7 || ^8
+ fuse.js: ^7
+ idb-keyval: ^6
+ jwt-decode: ^4
+ nprogress: ^0.2
+ qrcode: ^1.5
+ sortablejs: ^1
+ universal-cookie: ^7 || ^8
+ vue: ^3.5.0
+ peerDependenciesMeta:
+ async-validator:
+ optional: true
+ axios:
+ optional: true
+ change-case:
+ optional: true
+ drauu:
+ optional: true
+ focus-trap:
+ optional: true
+ fuse.js:
+ optional: true
+ idb-keyval:
+ optional: true
+ jwt-decode:
+ optional: true
+ nprogress:
+ optional: true
+ qrcode:
+ optional: true
+ sortablejs:
+ optional: true
+ universal-cookie:
+ optional: true
+
'@vueuse/metadata@12.8.2':
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
+ '@vueuse/metadata@14.2.1':
+ resolution: {integrity: sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==}
+
'@vueuse/shared@12.8.2':
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
+ '@vueuse/shared@14.2.1':
+ resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==}
+ peerDependencies:
+ vue: ^3.5.0
+
algoliasearch@5.49.2:
resolution: {integrity: sha512-1K0wtDaRONwfhL4h8bbJ9qTjmY6rhGgRvvagXkMBsAOMNr+3Q2SffHECh9DIuNVrMA1JwA0zCwhyepgBZVakng==}
engines: {node: '>= 14.0.0'}
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
birpc@2.9.0:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
@@ -857,9 +982,17 @@ packages:
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
engines: {node: '>=18'}
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ defu@6.1.7:
+ resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -1047,6 +1180,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ ohash@2.0.11:
+ resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
+
oniguruma-to-es@3.1.1:
resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==}
@@ -1065,6 +1201,10 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
+ postcss-selector-parser@6.0.10:
+ resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
+ engines: {node: '>=4'}
+
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -1084,6 +1224,11 @@ packages:
regex@6.1.0:
resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==}
+ reka-ui@2.9.6:
+ resolution: {integrity: sha512-K6bL457owpvWONc7hsjFxo3HDC9s6IzhRqShW0w9JSKelPGfRbkHD558UQTn/NH1cvrXVHygKyC7fExFmRketg==}
+ peerDependencies:
+ vue: '>= 3.4.0'
+
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
@@ -1137,6 +1282,12 @@ packages:
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ undici-types@7.19.2:
+ resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
+
unist-util-is@6.0.1:
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
@@ -1152,6 +1303,9 @@ packages:
unist-util-visit@5.1.0:
resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==}
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
vfile-message@4.0.3:
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
@@ -1216,6 +1370,17 @@ packages:
postcss:
optional: true
+ vue-demi@0.14.10:
+ resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+ engines: {node: '>=12'}
+ hasBin: true
+ peerDependencies:
+ '@vue/composition-api': ^1.0.0-rc.1
+ vue: ^3.0.0-0 || ^2.6.0
+ peerDependenciesMeta:
+ '@vue/composition-api':
+ optional: true
+
vue@3.5.30:
resolution: {integrity: sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==}
peerDependencies:
@@ -1356,6 +1521,8 @@ snapshots:
'@docsearch/css@3.8.2': {}
+ '@docsearch/css@4.6.2': {}
+
'@docsearch/js@3.8.2(@algolia/client-search@5.49.2)(search-insights@2.17.3)':
dependencies:
'@docsearch/react': 3.8.2(@algolia/client-search@5.49.2)(search-insights@2.17.3)
@@ -1367,6 +1534,8 @@ snapshots:
- react-dom
- search-insights
+ '@docsearch/js@4.6.2': {}
+
'@docsearch/react@3.8.2(@algolia/client-search@5.49.2)(search-insights@2.17.3)':
dependencies:
'@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.49.2)(algoliasearch@5.49.2)(search-insights@2.17.3)
@@ -1378,6 +1547,8 @@ snapshots:
transitivePeerDependencies:
- '@algolia/client-search'
+ '@docsearch/sidepanel-js@4.6.2': {}
+
'@esbuild/aix-ppc64@0.25.12':
optional: true
@@ -1456,12 +1627,45 @@ snapshots:
'@esbuild/win32-x64@0.25.12':
optional: true
+ '@floating-ui/core@1.7.5':
+ dependencies:
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/dom@1.7.6':
+ dependencies:
+ '@floating-ui/core': 1.7.5
+ '@floating-ui/utils': 0.2.11
+
+ '@floating-ui/utils@0.2.11': {}
+
+ '@floating-ui/vue@1.1.11(vue@3.5.30)':
+ dependencies:
+ '@floating-ui/dom': 1.7.6
+ '@floating-ui/utils': 0.2.11
+ vue-demi: 0.14.10(vue@3.5.30)
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - vue
+
'@iconify-json/simple-icons@1.2.73':
dependencies:
'@iconify/types': 2.0.0
'@iconify/types@2.0.0': {}
+ '@iconify/vue@5.0.0(vue@3.5.30)':
+ dependencies:
+ '@iconify/types': 2.0.0
+ vue: 3.5.30
+
+ '@internationalized/date@3.12.1':
+ dependencies:
+ '@swc/helpers': 0.5.21
+
+ '@internationalized/number@3.6.6':
+ dependencies:
+ '@swc/helpers': 0.5.21
+
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -1538,6 +1742,8 @@ snapshots:
'@oxfmt/binding-win32-x64-msvc@0.36.0':
optional: true
+ '@rive-app/canvas-lite@2.37.3': {}
+
'@rollup/rollup-android-arm-eabi@4.59.0':
optional: true
@@ -1653,6 +1859,10 @@ snapshots:
'@shikijs/vscode-textmate@10.0.2': {}
+ '@swc/helpers@0.5.21':
+ dependencies:
+ tslib: 2.8.1
+
'@tailwindcss/node@4.2.1':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -1714,12 +1924,24 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
- '@tailwindcss/vite@4.2.1(vite@6.4.2(jiti@2.6.1)(lightningcss@1.31.1))':
+ '@tailwindcss/typography@0.5.19(tailwindcss@4.2.1)':
+ dependencies:
+ postcss-selector-parser: 6.0.10
+ tailwindcss: 4.2.1
+
+ '@tailwindcss/vite@4.2.1(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))':
dependencies:
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
tailwindcss: 4.2.1
- vite: 6.4.2(jiti@2.6.1)(lightningcss@1.31.1)
+ vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)
+
+ '@tanstack/virtual-core@3.14.0': {}
+
+ '@tanstack/vue-virtual@3.13.24(vue@3.5.30)':
+ dependencies:
+ '@tanstack/virtual-core': 3.14.0
+ vue: 3.5.30
'@types/estree@1.0.8': {}
@@ -1740,16 +1962,55 @@ snapshots:
'@types/mdurl@2.0.0': {}
+ '@types/node@25.6.0':
+ dependencies:
+ undici-types: 7.19.2
+
'@types/unist@3.0.3': {}
'@types/web-bluetooth@0.0.21': {}
'@ungap/structured-clone@1.3.0': {}
- '@vitejs/plugin-vue@5.2.4(vite@6.4.2(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.30)':
+ '@vitejs/plugin-vue@5.2.4(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.30)':
+ dependencies:
+ vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)
+ vue: 3.5.30
+
+ '@voidzero-dev/vitepress-theme@4.8.4(focus-trap@7.8.0)(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))(vitepress@1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30)':
dependencies:
- vite: 6.4.2(jiti@2.6.1)(lightningcss@1.31.1)
+ '@docsearch/css': 4.6.2
+ '@docsearch/js': 4.6.2
+ '@docsearch/sidepanel-js': 4.6.2
+ '@iconify/vue': 5.0.0(vue@3.5.30)
+ '@rive-app/canvas-lite': 2.37.3
+ '@tailwindcss/typography': 0.5.19(tailwindcss@4.2.1)
+ '@tailwindcss/vite': 4.2.1(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))
+ '@vue/shared': 3.5.30
+ '@vueuse/core': 14.2.1(vue@3.5.30)
+ '@vueuse/integrations': 14.2.1(focus-trap@7.8.0)(vue@3.5.30)
+ '@vueuse/shared': 14.2.1(vue@3.5.30)
+ mark.js: 8.11.1
+ minisearch: 7.2.0
+ reka-ui: 2.9.6(vue@3.5.30)
+ tailwindcss: 4.2.1
+ vitepress: 1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3)
vue: 3.5.30
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+ - async-validator
+ - axios
+ - change-case
+ - drauu
+ - focus-trap
+ - fuse.js
+ - idb-keyval
+ - jwt-decode
+ - nprogress
+ - qrcode
+ - sortablejs
+ - universal-cookie
+ - vite
'@vue/compiler-core@3.5.30':
dependencies:
@@ -1832,6 +2093,13 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@vueuse/core@14.2.1(vue@3.5.30)':
+ dependencies:
+ '@types/web-bluetooth': 0.0.21
+ '@vueuse/metadata': 14.2.1
+ '@vueuse/shared': 14.2.1(vue@3.5.30)
+ vue: 3.5.30
+
'@vueuse/integrations@12.8.2(focus-trap@7.8.0)':
dependencies:
'@vueuse/core': 12.8.2
@@ -1842,14 +2110,28 @@ snapshots:
transitivePeerDependencies:
- typescript
+ '@vueuse/integrations@14.2.1(focus-trap@7.8.0)(vue@3.5.30)':
+ dependencies:
+ '@vueuse/core': 14.2.1(vue@3.5.30)
+ '@vueuse/shared': 14.2.1(vue@3.5.30)
+ vue: 3.5.30
+ optionalDependencies:
+ focus-trap: 7.8.0
+
'@vueuse/metadata@12.8.2': {}
+ '@vueuse/metadata@14.2.1': {}
+
'@vueuse/shared@12.8.2':
dependencies:
vue: 3.5.30
transitivePeerDependencies:
- typescript
+ '@vueuse/shared@14.2.1(vue@3.5.30)':
+ dependencies:
+ vue: 3.5.30
+
algoliasearch@5.49.2:
dependencies:
'@algolia/abtesting': 1.15.2
@@ -1867,6 +2149,10 @@ snapshots:
'@algolia/requester-fetch': 5.49.2
'@algolia/requester-node-http': 5.49.2
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
birpc@2.9.0: {}
ccount@2.0.1: {}
@@ -1881,8 +2167,12 @@ snapshots:
dependencies:
is-what: 5.5.0
+ cssesc@3.0.0: {}
+
csstype@3.2.3: {}
+ defu@6.1.7: {}
+
dequal@2.0.3: {}
detect-libc@2.1.2: {}
@@ -2066,6 +2356,8 @@ snapshots:
nanoid@3.3.11: {}
+ ohash@2.0.11: {}
+
oniguruma-to-es@3.1.1:
dependencies:
emoji-regex-xs: 1.0.0
@@ -2102,6 +2394,11 @@ snapshots:
picomatch@4.0.4: {}
+ postcss-selector-parser@6.0.10:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -2122,6 +2419,22 @@ snapshots:
dependencies:
regex-utilities: 2.3.0
+ reka-ui@2.9.6(vue@3.5.30):
+ dependencies:
+ '@floating-ui/dom': 1.7.6
+ '@floating-ui/vue': 1.1.11(vue@3.5.30)
+ '@internationalized/date': 3.12.1
+ '@internationalized/number': 3.6.6
+ '@tanstack/vue-virtual': 3.13.24(vue@3.5.30)
+ '@vueuse/core': 14.2.1(vue@3.5.30)
+ '@vueuse/shared': 14.2.1(vue@3.5.30)
+ aria-hidden: 1.2.6
+ defu: 6.1.7
+ ohash: 2.0.11
+ vue: 3.5.30
+ transitivePeerDependencies:
+ - '@vue/composition-api'
+
rfdc@1.4.1: {}
rollup@4.59.0:
@@ -2198,6 +2511,10 @@ snapshots:
trim-lines@3.0.1: {}
+ tslib@2.8.1: {}
+
+ undici-types@7.19.2: {}
+
unist-util-is@6.0.1:
dependencies:
'@types/unist': 3.0.3
@@ -2221,6 +2538,8 @@ snapshots:
unist-util-is: 6.0.1
unist-util-visit-parents: 6.0.2
+ util-deprecate@1.0.2: {}
+
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3
@@ -2231,7 +2550,7 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
- vite@6.4.2(jiti@2.6.1)(lightningcss@1.31.1):
+ vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.4)
@@ -2240,16 +2559,17 @@ snapshots:
rollup: 4.59.0
tinyglobby: 0.2.16
optionalDependencies:
+ '@types/node': 25.6.0
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.31.1
- vitepress-plugin-tabs@0.8.0(vitepress@1.6.4(@algolia/client-search@5.49.2)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30):
+ vitepress-plugin-tabs@0.8.0(vitepress@1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3))(vue@3.5.30):
dependencies:
- vitepress: 1.6.4(@algolia/client-search@5.49.2)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3)
+ vitepress: 1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3)
vue: 3.5.30
- vitepress@1.6.4(@algolia/client-search@5.49.2)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3):
+ vitepress@1.6.4(@algolia/client-search@5.49.2)(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)(postcss@8.5.8)(search-insights@2.17.3):
dependencies:
'@docsearch/css': 3.8.2
'@docsearch/js': 3.8.2(@algolia/client-search@5.49.2)(search-insights@2.17.3)
@@ -2258,7 +2578,7 @@ snapshots:
'@shikijs/transformers': 2.5.0
'@shikijs/types': 2.5.0
'@types/markdown-it': 14.1.2
- '@vitejs/plugin-vue': 5.2.4(vite@6.4.2(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.30)
+ '@vitejs/plugin-vue': 5.2.4(vite@6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.30)
'@vue/devtools-api': 7.7.9
'@vue/shared': 3.5.30
'@vueuse/core': 12.8.2
@@ -2267,7 +2587,7 @@ snapshots:
mark.js: 8.11.1
minisearch: 7.2.0
shiki: 2.5.0
- vite: 6.4.2(jiti@2.6.1)(lightningcss@1.31.1)
+ vite: 6.4.2(@types/node@25.6.0)(jiti@2.6.1)(lightningcss@1.31.1)
vue: 3.5.30
optionalDependencies:
postcss: 8.5.8
@@ -2301,6 +2621,10 @@ snapshots:
- universal-cookie
- yaml
+ vue-demi@0.14.10(vue@3.5.30):
+ dependencies:
+ vue: 3.5.30
+
vue@3.5.30:
dependencies:
'@vue/compiler-dom': 3.5.30
diff --git a/tsconfig.json b/tsconfig.json
index 94e02f3..3d7b530 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,13 +2,14 @@
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
- "moduleResolution": "bundler",
+ "moduleResolution": "node",
+ "types": ["node", "vitepress/client"],
"jsx": "preserve",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
- "include": [".vitepress/**/*.ts", ".vitepress/**/*.vue"],
+ "include": ["docs/.vitepress/**/*.ts", "docs/.vitepress/**/*.vue", "docs/.vitepress/**/*.d.ts"],
"exclude": ["node_modules"]
}