Framework Maintainability Guide
Overview
This page records the maintenance rules for CrychicDoc's framework layer. When a change touches shared fields, runtime behavior, component structure, or Markdown plugins, field definitions, defaults, and shaping logic should be updated before runtime and views.
Shared problems should be solved in shared layers instead of being patched at page level.
Related Pages
Source-of-Truth Structure
- Runtime and APIs:
.vitepress/utils/vitepress/** - Theme components:
.vitepress/theme/components/** - Locale resources:
.vitepress/config/locale/** - Markdown plugins:
.vitepress/plugins/** - Plugin registration:
.vitepress/config/markdown-plugins.ts
All internal imports must use project aliases (@utils, @config, @components) to keep refactors stable.
Extension APIs (No Core Edits Required)
1. Typography Style Registry
API: @utils/vitepress/api/frontmatter/hero/HeroTypographyRegistryApi
import { heroTypographyRegistry } from "@utils/vitepress/api/frontmatter/hero";
heroTypographyRegistry.registerStyle({
type: "editorial-soft",
aliases: ["soft-editorial"],
motion: {
intensity: 0.9,
title: { x: 6, y: -4, scale: 1.03 },
text: { x: 8, y: 3, scale: 1.02 },
tagline: { x: 4, y: 6, scale: 1.01 },
image: { x: 5, y: -2, scale: 1.015 },
transitionDuration: 520,
transitionDelayStep: 36,
transitionEasing: "cubic-bezier(0.2, 0.9, 0.2, 1)",
},
});Then use hero.typography.type: editorial-soft in frontmatter.
No core typography runtime changes are required.
2. Navigation Dropdown Layout Registry
API: @utils/vitepress/api/navigation/NavDropdownLayoutRegistryApi
import { navDropdownLayoutRegistry } from "@utils/vitepress/api/navigation";
import VPNavLayoutEditorial from "@components/navigation/layouts/VPNavLayoutEditorial.vue";
navDropdownLayoutRegistry.registerLayout("editorial", VPNavLayoutEditorial);Then in nav config:
dropdown: {
layout: "editorial",
panels: [...]
}Alternative override (per-item):
dropdown: {
layoutComponent: "VPNavLayoutEditorial",
panels: [...]
}3. Floating Element Type Registry
API: @utils/vitepress/api/frontmatter/hero/FloatingElementRegistryApi
import { floatingElementRegistry } from "@utils/vitepress/api/frontmatter/hero";
floatingElementRegistry.registerType({
type: "keyword-chip",
renderAs: "badge",
className: "floating-keyword-chip",
});Frontmatter usage:
hero:
floating:
items:
- type: keyword-chip
text: Event APIFor fully custom UI, bind to a registered Vue component:
hero:
floating:
items:
- component: HeroFloatingCourseCard
componentProps:
title: KubeJS Course
provider: GitBookCreating a New Component
- Create the component file under
.vitepress/theme/components/<category>/. - Export it from the corresponding registry barrel under
.vitepress/utils/vitepress/componentRegistry/if it must be reusable globally. - Register it in
.vitepress/utils/vitepress/components.tsif Markdown/global component usage is required. - Add locale JSON files under:
.vitepress/config/locale/en-US/components/....vitepress/config/locale/zh-CN/components/...
- Add or update component ID mapping in
.vitepress/config/locale/component-id-mapping.json.
Minimal i18n component pattern:
<script setup lang="ts">
import { useSafeI18n } from "@utils/i18n/locale";
const { t } = useSafeI18n("my-component", {
title: "Default title",
});
</script>
<template>
<h2>{{ t.title }}</h2>
</template>i18n System Rules
useSafeI18nnow resolves locale reactively from VitePress language state.- Translation buckets are cached per
componentId@locale. - Locale switches update component text without reload.
- Missing keys automatically fall back to default translations passed in code.
- Component file-path mapping is resolved from
component-id-mapping.json.
This design is resilient for dynamic locale switches and avoids stale translation state from singleton non-reactive access.
Adding a New Markdown Plugin
- Add plugin implementation in
.vitepress/plugins/. - Wire it in
.vitepress/config/markdown-plugins.ts. - If plugin output needs a custom component, register the component via
components.ts. - Add localized usage examples in docs pages.
Theme Sync Standard (First Enter + Reload Safe)
Use this standard for every component that has theme-sensitive visuals (hero backgrounds, themed assets, icon sets, metadata-like visual blocks, nav cards).
- Do not read DOM theme classes directly in feature components.
- Do not use raw
useData().isDarkas the sole source for first-paint visuals. - Use
getThemeRuntime(isDark)and consumeeffectiveDark,themeReady, andversionfor visual decisions that must be stable on first enter, reload, and runtime toggle. - Inside hero descendants, use
useHeroTheme()and preferisDarkRef.valueplusresolveThemeValueByMode(...)(or the sibling helpersresolveThemeColorByMode/resolveThemeSourceByMode). - For first-paint-sensitive hero parts, gate rendering with
themeReadyinVPHeroto avoid light/dark flash. - Never fall back from dark to light or light to dark automatically. Shared resolvers (
resolveThemeValueByMode,resolveThemeColorByMode,resolveThemeSourceByMode) follow one rule: prefer the theme-specific value, and only then fall back to the sharedvalue. - Component folders stay view-only. If theme sync needs observers, scheduling, or shared lifecycle, move that logic into
.vitepress/utils/vitepress/runtime/theme/**.
Reference APIs:
@utils/vitepress/runtime/theme/themeRuntime(getThemeRuntime)@utils/vitepress/runtime/theme/heroThemeContext@utils/vitepress/runtime/theme/themeValueResolver
Minimal pattern:
import { useData } from "vitepress";
import { getThemeRuntime } from "@utils/vitepress/runtime/theme";
const { isDark } = useData();
const { effectiveDark, themeReady, version } = getThemeRuntime(isDark);Resize Sync Standard
All resize-sensitive components must use the shared resize runtime instead of ad-hoc observers.
- Use
createElementResizeState(targetRef, onResize, { debounceMs }). - Re-observe once the target element exists (
if (targetRef.value) reobserve(targetRef.value)). - Do not create manual
ResizeObserverinstances unless the shared API cannot satisfy a special case. - Keep cleanup lifecycle in the shared runtime, not duplicated per component.
Reference API:
@utils/vitepress/runtime/viewport/elementResizeState
Minimal pattern:
import { createElementResizeState } from "@utils/vitepress/runtime/viewport";
const targetRef = ref<HTMLElement | null>(null);
const { reobserve } = createElementResizeState(
targetRef,
() => syncLayout(),
{ debounceMs: 80 },
);Day-to-Day Change Sequence
Use this order when changing framework behavior so field definitions, runtime logic, and components do not drift apart:
- Change definitions and shaping first.
Update schema, types, and shaping logic in
.vitepress/utils/vitepress/api/**. - Change shared runtime second.
Put stateful lifecycle, DOM observation, viewport sync, theme sync, or adaptive logic in
.vitepress/utils/vitepress/runtime/**. - Change view components third.
Keep
.vitepress/theme/components/**focused on rendering and light composition. - Update examples and docs immediately. If a new frontmatter key or extension point exists, add at least one markdown example and document the intended usage.
- Run the matching verification commands before merge.
Recommended command sequence from repo root:
yarn locale
yarn tags
yarn docs:buildThe sidebar is generated as part of development and build. It does not need its own separate maintenance command.
Code Placement Guide
Use these directories as the primary source of truth:
.vitepress/config/project-config.tsSite-level product settings, feature toggles, language list, search provider, deployment, social links..vitepress/config/lang/**Nav/theme/search locale modules..vitepress/config/shaders/**Built-in shader templates and shader registry..vitepress/config/markdown-plugins.tsMarkdown plugin composition and registration order..vitepress/plugins/**Markdown-it plugin implementations..vitepress/theme/components/**Vue rendering layer. Components should stay thin and consume normalized config/runtime state..vitepress/theme/styles/**Global style layers, variables, plugin styles, shared component CSS..vitepress/utils/vitepress/api/**Schemas, shaping logic, registries, field types, extension entry points..vitepress/utils/vitepress/runtime/**Stateful domains such as theme sync, viewport sync, hero behavior, media/runtime observers..vitepress/utils/vitepress/componentRegistry/**Reusable export barrels for globally shared components..vitepress/utils/vitepress/components.tsFinal global component registration for markdown/runtime use.
Rule of thumb:
- If it parses or validates config, it belongs in
api. - If it owns lifecycle or DOM coordination, it belongs in
runtime. - If it just renders props/state, it belongs in
theme/components.
Runtime and Function Extension Playbook
When adding a new function, composable, service, or controller:
- Put pure field-shaping helpers in
api, nottheme/components. - Put stateful controllers in
runtime, preferably as small class-based modules when lifecycle is non-trivial. - Export new public APIs from the nearest
index.tsbarrel. - Avoid direct DOM reads in feature components when the behavior is shared or timing-sensitive.
- Prefer one shared observer/runtime over repeated
MutationObserverorResizeObserverinstances inside many components.
Good examples already in the framework:
- Theme stabilization:
.vitepress/utils/vitepress/runtime/theme/** - Element resize runtime:
.vitepress/utils/vitepress/runtime/viewport/** - Hero nav adaptation:
.vitepress/utils/vitepress/runtime/hero/navAdaptiveState.ts
Component and Global Registration Playbook
When adding a new Vue component:
- Create it under the correct folder in
.vitepress/theme/components/<category>/. - If it should be imported by other framework code, export it from the matching barrel in
.vitepress/utils/vitepress/componentRegistry/**. - If markdown users should be able to write it directly, register it in
.vitepress/utils/vitepress/components.ts. - If it has UI text, add locale resources and keep component ID mapping synchronized.
For markdown-facing components, the registry chain should stay:
component file -> componentRegistry barrel -> components.ts -> markdown/runtime consumption
Configuration Extension Playbook
For new configuration or frontmatter fields:
- Add the type to
.vitepress/utils/vitepress/api/frontmatter/hero/HeroFrontmatterApi.tsor the relevant API module. - Normalize legacy and modern forms in the same API layer.
- Keep rendering components consuming normalized values only.
- If the field affects nav/search/theme behavior, update the matching runtime controller rather than duplicating logic in the component.
- Add a docs example page and update the maintainability/reference docs in the same change.
For site-level feature changes:
- Update
.vitepress/config/project-config.ts. - Update locale config under
.vitepress/config/lang/**if labels or search locales change. - Run
yarn docs:buildwhen the change needs to propagate into generated metadata or output.
Style Extension Playbook
Global styling is layered deliberately. Follow the import order in .vitepress/theme/styles/index.css:
- Config variables
- Base styles
- Plugin styles
- Shared component styles
Use the right style vehicle for the job:
- Scoped
<style>in a component: Use for component-local layout and visuals. - Global CSS under
.vitepress/theme/styles/**: Use for cross-component tokens, plugin skinning, layout primitives, and theme-wide selectors. - CSS variables via frontmatter/config: Use for runtime-themable values, especially hero/background/nav/search colors.
Do not introduce ad-hoc global selectors when a CSS variable rule or scoped style is enough.
Hero Extension Playbook
For the full hero extension guide — including typography styles, floating elements, shader templates, background renderers, and nav/search visuals — see the dedicated page:
→
Documentation and Verification Checklist
Every framework-facing extension should ship with:
- Type updates
- Runtime/component integration
- At least one markdown example
- Locale/doc updates in both languages when applicable
- Verification commands recorded in the PR or handoff
Minimum verification for framework work:
yarn localeyarn tagsyarn docs:build
Maintenance Rules
- Keep APIs small and class-based where lifecycle/state is non-trivial.
- Do not add compatibility re-export stubs for deprecated paths.
- Use alias imports only (
@...) for internal code. - Keep extension points registration-driven.
- Validate with:
npx tsc --noEmit