refactor(mcp): drop dead tree-build helpers from element-tool-helpers

Post-42/42 delegate refactor: pen-mcp handlers all import
assignIdsRecursively + buildScrollWrapper + CJK helpers from
@zseven-w/pen-core/element-builders. The local copies in
element-tool-helpers.ts had zero callers and could drift from
the canonical pen-core versions. Re-export from pen-core so any
external caller that was still reaching in by path keeps
compiling without churn.

File shrinks from ~276 to ~189 lines. Kept: ensureParentExists,
insertElementTree (with its simulateDslParentResolve helper,
rollback + post-insert verification) — these are pen-mcp-specific
server I/O / integrity logic that have no place in pen-core.
This commit is contained in:
Fini 2026-04-22 00:35:29 +08:00
parent d10ff757c3
commit 05cfa53795

View file

@ -7,38 +7,20 @@ import {
resolveDocPath,
} from '../document-manager';
import { findNodeInTree, findParentInTree, getDocChildren } from '../utils/node-operations';
import { generateId } from '../utils/id';
import { handleBatchDesign } from './batch-design';
export type CjkScript = null | 'chinese' | 'japanese' | 'korean';
export function detectCjkScript(s: string): CjkScript {
// Hiragana U+3040-309F + Katakana U+30A0-30FF → Japanese
if (/[\u3040-\u309f\u30a0-\u30ff]/.test(s)) return 'japanese';
// Hangul Syllables U+AC00-D7AF → Korean
if (/[\uac00-\ud7af]/.test(s)) return 'korean';
// CJK Symbols / Punctuation U+3000-303F + CJK Unified Ideographs U+4E00-9FFF → Chinese
if (/[\u3000-\u303f\u4e00-\u9fff]/.test(s)) return 'chinese';
return null;
}
/**
* Map a CJK script to its Noto Sans font family. Returns undefined for
* non-CJK so callers can fall back to theme default (Inter for body,
* theme heading for headings).
*/
export function cjkFontFamily(script: CjkScript): string | undefined {
switch (script) {
case 'japanese':
return 'Noto Sans JP';
case 'korean':
return 'Noto Sans KR';
case 'chinese':
return 'Noto Sans SC';
default:
return undefined;
}
}
// CJK script detection + font-family dispatch + tree-build helpers all
// moved to @zseven-w/pen-core/element-builders so apps/web client shims
// and pen-mcp handlers share identical logic. Re-export here so any
// external caller that imported these from `./element-tool-helpers`
// keeps compiling without churn.
export {
assignIdsRecursively,
buildScrollWrapper,
cjkFontFamily,
detectCjkScript,
type CjkScript,
} from '@zseven-w/pen-core';
/**
* Simulate how batch-design.ts:resolveRef will interpret a JSON.stringify'd
@ -57,8 +39,7 @@ function simulateDslParentResolve(parentId: string): string {
* handleBatchDesign. batch_design's underlying `insertNodeInTree` silently
* returns the original tree when the parent is missing, producing a
* success-looking response (binding + nodeId) with an orphaned node that
* never lands on disk. Element tools (add_scroll_row_v0 / add_bottom_nav_v0
* / add_activity_ring_v0) must fail fast with a clear error instead.
* never lands on disk. Element tools must fail fast with a clear error.
*
* Skips validation when parent_id is falsy (root-level insertion is always
* valid). Throws a descriptive Error otherwise.
@ -82,63 +63,6 @@ export async function ensureParentExists(params: {
}
}
/**
* Walk a node subtree and stamp every node with a fresh id. batch_design's
* downstream DSL only assigns an id to the TOP-level inserted node —
* nested children arrive unchanged, leaving them unreferenceable by any
* later tree operation.
*/
export function assignIdsRecursively(node: Record<string, unknown>): void {
if (typeof node.id !== 'string') node.id = generateId();
const children = node.children;
if (Array.isArray(children)) {
for (const child of children) {
if (child && typeof child === 'object') {
assignIdsRecursively(child as Record<string, unknown>);
}
}
}
}
/**
* Build the canonical scroll-row wrapper taught in
* `packages/pen-ai-skills/skills/phases/generation/overflow.md` §HORIZONTAL
* SCROLL ROWS: outer wrapper (fill_container + clipContent + vertical) >
* inner row (fit_content + horizontal + gap + padding=[0,20]) > children.
*
* Shared by all three narrow row tools (add_card_row_v0 /
* add_metric_row_v0 / add_nav_chip_row_v0). Each tool only differs in the
* per-item node builder; the wrapper is identical.
*/
export function buildScrollWrapper(opts: {
rowName: string;
innerChildren: Record<string, unknown>[];
gap: number;
}): Record<string, unknown> {
return {
type: 'frame',
name: opts.rowName,
role: 'scroll-row-wrapper',
width: 'fill_container',
height: 'fit_content',
layout: 'vertical',
clipContent: true,
children: [
{
type: 'frame',
name: 'Scroll Inner Row',
role: 'scroll-row',
width: 'fit_content',
height: 'fit_content',
layout: 'horizontal',
gap: opts.gap,
padding: [0, 20],
children: opts.innerChildren,
},
],
};
}
/**
* Insert a fully-built node subtree via handleBatchDesign's single-insert
* DSL. Centralizes the parent_ref serialization + batch_design call