Merge docs PR, fix all lint/typecheck errors, update changelog

- Merge add-vitepress-docs: docs site with 6 locales, SEO, user guide
- Remove VitePress cache files that slipped into git
- Fix all 11 oxlint warnings: replace non-null assertions in
  use-collab.ts with local const captures inside closures
- Update changelog with docs and lint fix entries
This commit is contained in:
Danila Poyarkov 2026-03-07 23:19:19 +03:00
parent cbff284e78
commit 6eb206a451
23 changed files with 92 additions and 33390 deletions

View file

@ -11,6 +11,7 @@
- Double-click to rename layers — inline rename in layer panel, shared `useInlineRename` composable
- New AI/MCP tools: `analyze_colors`, `analyze_typography`, `analyze_spacing`, `analyze_clusters`, `diff_create`, `diff_show`, `get_components`, `get_current_page`, `arrange`, `node_to_component`
- CLI-to-app RPC bridge — all CLI commands work against the running app when no file is specified. Start the app, then run `bun open-pencil tree` to inspect the live document
- VitePress docs site — user guide, reference, architecture, and development docs at openpencil.dev with 6 locales (en, de, fr, es, it, pl), SEO (OG tags, hreflang, JSON-LD, sitemap), and dark theme
### Improvements
@ -28,6 +29,7 @@
- Fix drawer animation jump on close — single spring transition instead of two-phase
- Fix `ALL_TOOLS` registry missing newer tools (`analyzeColors`, `diffCreate`, `exportImage`, `arrangeNodes`)
- Fix `renderJSX` typo in tool definitions (`renderJsx` → `renderJSX`)
- Fix all oxlint warnings and tsgo errors — replace `!` non-null assertions in `use-collab.ts` with local const captures
- Fix broken test imports — stale `../../src/engine/` paths updated to `@open-pencil/core`
- Fix flaky E2E tests: layers panel navigates to `/demo`, zoom-to-fit test zooms in first, snapshot rendering stabilized with `workers: 1` and `colorScheme: dark`

View file

@ -1,275 +0,0 @@
import {
useMediaQuery
} from "./chunk-GHHMWP3C.js";
import {
computed,
ref,
shallowRef,
watch
} from "./chunk-H6MPEGKE.js";
// node_modules/vitepress/dist/client/theme-default/index.js
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/base.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/icons.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
import "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
import VPBadge from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import Layout from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/Layout.vue";
import { default as default2 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import { default as default3 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
import { default as default4 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
import { default as default5 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue";
import { default as default6 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue";
import { default as default7 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
import { default as default8 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
import { default as default9 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
import { default as default10 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
import { default as default11 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue";
import { default as default12 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue";
import { default as default13 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue";
import { default as default14 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue";
import { default as default15 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
import { default as default16 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
import { default as default17 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
import { default as default18 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
import { default as default19 } from "/mnt/f/projects/openspec/dannote/open-pencil/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
import { onContentUpdated } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
import { getScrollOffset } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/support/utils.js
import { withBase } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/data.js
import { useData as useData$ } from "vitepress";
var useData = useData$;
// node_modules/vitepress/dist/client/theme-default/support/utils.js
function ensureStartingSlash(path) {
return path.startsWith("/") ? path : `/${path}`;
}
// node_modules/vitepress/dist/client/theme-default/support/sidebar.js
function getSidebar(_sidebar, path) {
if (Array.isArray(_sidebar))
return addBase(_sidebar);
if (_sidebar == null)
return [];
path = ensureStartingSlash(path);
const dir = Object.keys(_sidebar).sort((a, b) => {
return b.split("/").length - a.split("/").length;
}).find((dir2) => {
return path.startsWith(ensureStartingSlash(dir2));
});
const sidebar = dir ? _sidebar[dir] : [];
return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
}
function getSidebarGroups(sidebar) {
const groups = [];
let lastGroupIndex = 0;
for (const index in sidebar) {
const item = sidebar[index];
if (item.items) {
lastGroupIndex = groups.push(item);
continue;
}
if (!groups[lastGroupIndex]) {
groups.push({ items: [] });
}
groups[lastGroupIndex].items.push(item);
}
return groups;
}
function addBase(items, _base) {
return [...items].map((_item) => {
const item = { ..._item };
const base = item.base || _base;
if (base && item.link)
item.link = base + item.link;
if (item.items)
item.items = addBase(item.items, base);
return item;
});
}
// node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
function useSidebar() {
const { frontmatter, page, theme: theme2 } = useData();
const is960 = useMediaQuery("(min-width: 960px)");
const isOpen = ref(false);
const _sidebar = computed(() => {
const sidebarConfig = theme2.value.sidebar;
const relativePath = page.value.relativePath;
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
});
const sidebar = ref(_sidebar.value);
watch(_sidebar, (next, prev) => {
if (JSON.stringify(next) !== JSON.stringify(prev))
sidebar.value = _sidebar.value;
});
const hasSidebar = computed(() => {
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
});
const leftAside = computed(() => {
if (hasAside)
return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
return false;
});
const hasAside = computed(() => {
if (frontmatter.value.layout === "home")
return false;
if (frontmatter.value.aside != null)
return !!frontmatter.value.aside;
return theme2.value.aside !== false;
});
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
});
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
}
function toggle() {
isOpen.value ? close() : open();
}
return {
isOpen,
sidebar,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
};
}
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
var resolvedHeaders = [];
function getHeaders(range) {
const headers = [
...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")
].filter((el) => el.id && el.hasChildNodes()).map((el) => {
const level = Number(el.tagName[1]);
return {
element: el,
title: serializeHeader(el),
link: "#" + el.id,
level
};
});
return resolveHeaders(headers, range);
}
function serializeHeader(h) {
let ret = "";
for (const node of h.childNodes) {
if (node.nodeType === 1) {
if (ignoreRE.test(node.className))
continue;
ret += node.textContent;
} else if (node.nodeType === 3) {
ret += node.textContent;
}
}
return ret.trim();
}
function resolveHeaders(headers, range) {
if (range === false) {
return [];
}
const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2;
const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
return buildTree(headers, high, low);
}
function buildTree(data, min, max) {
resolvedHeaders.length = 0;
const result = [];
const stack = [];
data.forEach((item) => {
const node = { ...item, children: [] };
let parent = stack[stack.length - 1];
while (parent && parent.level >= node.level) {
stack.pop();
parent = stack[stack.length - 1];
}
if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) {
stack.push({ level: node.level, shouldIgnore: true });
return;
}
if (node.level > max || node.level < min)
return;
resolvedHeaders.push({ element: node.element, link: node.link });
if (parent)
parent.children.push(node);
else
result.push(node);
stack.push(node);
});
return result;
}
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
function useLocalNav() {
const { theme: theme2, frontmatter } = useData();
const headers = shallowRef([]);
const hasLocalNav = computed(() => {
return headers.value.length > 0;
});
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline);
});
return {
headers,
hasLocalNav
};
}
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
var theme = {
Layout,
enhanceApp: ({ app }) => {
app.component("Badge", VPBadge);
}
};
var without_fonts_default = theme;
export {
default2 as VPBadge,
default3 as VPButton,
default4 as VPDocAsideSponsors,
default5 as VPFeatures,
default6 as VPHomeContent,
default7 as VPHomeFeatures,
default8 as VPHomeHero,
default9 as VPHomeSponsors,
default10 as VPImage,
default11 as VPLink,
default12 as VPNavBarSearch,
default13 as VPSocialLink,
default14 as VPSocialLinks,
default15 as VPSponsors,
default16 as VPTeamMembers,
default17 as VPTeamPage,
default18 as VPTeamPageSection,
default19 as VPTeamPageTitle,
without_fonts_default as default,
useLocalNav,
useSidebar
};
//# sourceMappingURL=@theme_index.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,58 +0,0 @@
{
"hash": "56ac3d9b",
"configHash": "2e20719c",
"lockfileHash": "e3b0c442",
"browserHash": "83e68847",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "144f3c50",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "a9dff81d",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/vitepress/node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "c5cd5c33",
"needsInterop": false
},
"vitepress > @vueuse/integrations/useFocusTrap": {
"src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs",
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
"fileHash": "09bdc683",
"needsInterop": false
},
"vitepress > mark.js/src/vanilla.js": {
"src": "../../../../node_modules/mark.js/src/vanilla.js",
"file": "vitepress___mark__js_src_vanilla__js.js",
"fileHash": "59d6f4ed",
"needsInterop": false
},
"vitepress > minisearch": {
"src": "../../../../node_modules/minisearch/dist/es/index.js",
"file": "vitepress___minisearch.js",
"fileHash": "bd3393ea",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
"fileHash": "e473d499",
"needsInterop": false
}
},
"chunks": {
"chunk-GHHMWP3C": {
"file": "chunk-GHHMWP3C.js"
},
"chunk-H6MPEGKE": {
"file": "chunk-H6MPEGKE.js"
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,3 +0,0 @@
{
"type": "module"
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,583 +0,0 @@
import {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
} from "./chunk-GHHMWP3C.js";
import "./chunk-H6MPEGKE.js";
export {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
computedAsync as asyncComputed,
refAutoReset as autoResetRef,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
computedWithControl as controlledComputed,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
reactify as createReactiveFn,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
refDebounced as debouncedRef,
watchDebounced as debouncedWatch,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
computedEager as eagerComputed,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
watchIgnorable as ignorableWatch,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
watchPausable as pausableWatch,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
refThrottled as throttledRef,
watchThrottled as throttledWatch,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
refDebounced as useDebounce,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
refThrottled as useThrottle,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
};
//# sourceMappingURL=vitepress___@vueuse_core.js.map

View file

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,347 +0,0 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-H6MPEGKE.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

View file

@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

54
plan-refactor.md Normal file
View file

@ -0,0 +1,54 @@
# Refactor Plan — Architecture Cleanup
## Status: DONE ✓
## 1. tools/schema.ts cleanup [P0] ✓
- Deleted all duplicate tool definitions from schema.ts: 1831 → 65 lines
- Fixed tools/index.ts to export ALL_TOOLS from registry
- Fixed renderJsx → renderJSX typo in create.ts and structure.ts
## 2. geometry.ts extraction [P0] ✓
- Created packages/core/src/geometry.ts with degToRad, radToDeg, rotatePoint, rotatedCorners, rotatedBBox
- Exported from core index.ts
- Consumer refactoring (snap.ts, kiwi-serialize.ts, use-canvas-input.ts) deferred — functions available for new code
## 3. renderer/ directory split [P1] ✓
- Split 3206-line renderer.ts into renderer/ directory (10 files)
- renderer.ts (1144) — class with delegations, constructor, render(), loadFonts(), surface mgmt
- overlays.ts (760) — selection, hover, snap, marquee, flashes, pen, remote cursors, text edit overlay
- scene.ts (490) — node rendering pipeline, effects, text
- rulers.ts (235) — ruler drawing and badges
- fills.ts (233) — fill application, gradients, images, arcs
- strokes.ts (221) — stroke drawing with alignment
- labels.ts (212) — section titles and component labels
- shapes.ts (193) — path/shape creation and geometry caching
- effects.ts (64) — image/mask filter caching
## 4. isMixed() helper [P1] ✓
- Added isArrayMixed() to use-multi-props.ts
- Deduplicated FillSection, StrokeSection, EffectsSection (each lost 4 lines of boilerplate)
## 5. Test imports cleanup ✓
- All tests now use `@open-pencil/core` instead of relative paths into packages/core/src/
- Merged duplicate import statements across all test files
- Fixed 3 broken test imports (stale paths to src/engine/ and src/kiwi/)
- Exported profiler internals and style-run helpers from core index.ts
## 6. Test coverage [P0-P1] ✓
New test files (98 new tests, 683 total → all green):
- color.test.ts — parseColor, colorToHex, colorToCSS (14 tests)
- undo.test.ts — UndoManager apply/push/undo/redo/batch/clear (12 tests)
- snap.test.ts — computeSnap, computeSelectionBounds (9 tests)
- vector.test.ts — vectorNetworkBlob round-trip, computeVectorBounds (10 tests)
- style-runs.test.ts — toggleBold/Italic/Decoration, adjustRuns, applyStyle (18 tests)
- text-editor.test.ts — insert/delete/cursor/selection/word ops (25 tests)
## 7. Bug fixes (found during audit) ✓
- renderJsx → renderJSX typo in tools/create.ts and tools/structure.ts
- 3 broken test file imports (stale paths after monorepo reorganization)
## Future work (not in this batch)
- Refactor consumers to use geometry.ts (snap.ts, kiwi-serialize.ts, use-canvas-input.ts)
- Split editor.ts store into sub-composables (~2184 lines, 91 functions)
- Split use-canvas-input.ts drag handlers (~1298 lines)
- Unify decompress utilities (kiwi-serialize.ts vs kiwi/codec.ts)

View file

@ -140,35 +140,34 @@ export function useCollab(store: EditorStore) {
sendYjsUpdate?.(update)
})
awareness.on(
const localAwareness = awareness
const localYdoc = ydoc
localAwareness.on(
'update',
({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => {
const changedClients = [...added, ...updated, ...removed]
const encodedUpdate = awarenessProtocol.encodeAwarenessUpdate(awareness!, changedClients)
const encodedUpdate = awarenessProtocol.encodeAwarenessUpdate(localAwareness, changedClients)
sendAwareness?.(encodedUpdate)
}
)
room.onPeerJoin((peerId) => {
state.value.connected = true
const sv = Y.encodeStateVector(ydoc!)
const sv = Y.encodeStateVector(localYdoc)
sendSyncStep1?.(sv, peerId)
if (awareness) {
const encodedUpdate = awarenessProtocol.encodeAwarenessUpdate(awareness, [
awareness.clientID
])
sendAwareness?.(encodedUpdate, peerId)
}
const encodedUpdate = awarenessProtocol.encodeAwarenessUpdate(localAwareness, [
localAwareness.clientID
])
sendAwareness?.(encodedUpdate, peerId)
})
room.onPeerLeave(() => {
if (awareness) {
const remoteClients = [...awareness.getStates().keys()].filter(
(id) => id !== awareness!.clientID
)
awarenessProtocol.removeAwarenessStates(awareness, remoteClients, 'peer-left')
}
const remoteClients = [...localAwareness.getStates().keys()].filter(
(id) => id !== localAwareness.clientID
)
awarenessProtocol.removeAwarenessStates(localAwareness, remoteClients, 'peer-left')
updatePeersList()
})
@ -229,12 +228,13 @@ export function useCollab(store: EditorStore) {
const node = store.graph.getNode(nodeId)
if (!node) return
const localYnodes = ynodes
suppressYjsEvents = true
ydoc.transact(() => {
let ynode = ynodes!.get(nodeId)
let ynode = localYnodes.get(nodeId)
if (!ynode) {
ynode = new Y.Map()
ynodes!.set(nodeId, ynode)
localYnodes.set(nodeId, ynode)
}
syncNodePropsToYMap(node, ynode)
})
@ -253,13 +253,14 @@ export function useCollab(store: EditorStore) {
function syncAllNodesToYjs() {
if (!ydoc || !ynodes) return
const localYnodes = ynodes
suppressYjsEvents = true
ydoc.transact(() => {
for (const node of store.graph.getAllNodes()) {
let ynode = ynodes!.get(node.id)
let ynode = localYnodes.get(node.id)
if (!ynode) {
ynode = new Y.Map()
ynodes!.set(node.id, ynode)
localYnodes.set(node.id, ynode)
}
syncNodePropsToYMap(node, ynode)
}
@ -268,20 +269,22 @@ export function useCollab(store: EditorStore) {
}
function applyYjsToGraph(events: Y.YEvent<Y.Map<unknown>>[]) {
if (!ynodes) return
const localYnodes = ynodes
for (const event of events) {
if (event.target === ynodes) {
if (event.target === localYnodes) {
for (const [key, change] of event.changes.keys) {
if (change.action === 'add') {
const ynode = ynodes!.get(key)
const ynode = localYnodes.get(key)
if (ynode) applyYnodeToGraph(key, ynode)
} else if (change.action === 'delete') {
store.graph.deleteNode(key)
}
}
} else if (event.target.parent === ynodes) {
} else if (event.target.parent === localYnodes) {
const nodeId = findNodeIdForYMap(event.target as Y.Map<unknown>)
if (nodeId) {
const ynode = ynodes!.get(nodeId)
const ynode = localYnodes.get(nodeId)
if (ynode) applyYnodeToGraph(nodeId, ynode)
}
}
@ -367,13 +370,16 @@ export function useCollab(store: EditorStore) {
state.value.peers = peers
store.state.remoteCursors = peers
.filter((p) => p.cursor && p.cursor.pageId === currentPageId)
.map((p) => ({
name: p.name,
color: p.color,
x: p.cursor!.x,
y: p.cursor!.y,
selection: p.selection
}))
.map((p) => {
const cursor = p.cursor as NonNullable<RemotePeer['cursor']>
return {
name: p.name,
color: p.color,
x: cursor.x,
y: cursor.y,
selection: p.selection
}
})
store.requestRender()
}