name: easyink-material-dev description: EasyInk material development and review guide. Use when implementing, extending, debugging, or reviewing built-in or custom EasyInk materials that add or change a Schema-saved visual element across MaterialNode shape, default-node factories, Designer registration/rendering/editing, Viewer registration/rendering/measurement, orthogonal page layout behavior, repeated overlays, fragment pagination, datasource binding, Assistant material knowledge, tests, and i18n.
EasyInk Material Dev
Use this skill to work on EasyInk materials as complete system features, not isolated render functions. A material change is only done when Schema, Designer, Viewer, page behavior, binding, catalog exposure, Assistant knowledge, tests, and locale coverage still agree.
If the request adds a panel, command, diagnostic subscription, host workflow, or toolbar action around existing elements without adding/changing a Schema-saved visual element, use $easyink-contribution-dev instead.
First Read
Start from the current repo, not memory. Always read the touched material package plus the closest existing material with the same behavior.
Core files:
CLAUDE.mdfor project coding rules: nostructuredClone, no Unicode emoji, workspace deps, andpnpm build,pnpm lint,pnpm typecheckin order for broad validation..github/architecture/24-page-layout-orthogonal-system.mdfor the current page model, layout, reflow, pagination, page overlay, and editor surface rules..github/architecture/26-conditional-rendering.mdfor the default material condition capability,remove/reservesemantics, and when a material may explicitly disable or narrow condition behavior.docs/advanced/custom-materials.mdfor the public custom material contract.docs/advanced/schema.mdforDocumentSchemaInputnormalization, page layers, and persistent schema fields.packages/schema/src/types.ts,packages/schema/src/defaults.ts, andpackages/schema/src/validation.tsforMaterialNode, binding, page layer defaults, and schema validity rules.packages/core/src/font.ts,packages/core/src/material-data-contract.ts, andpackages/viewer/src/font-loader.tsforFontProvider,FontManager, material data contract resolution, font collection, caching, and host-document@font-faceinjection.packages/core/src/material-extension.tsandpackages/core/src/material-viewer.tsfor Designer and Viewer extension contracts.packages/core/src/layout-strategy.ts,packages/core/src/reflow-engine.ts,packages/core/src/pagination-engine.ts, andpackages/core/src/editor-surface-plan.tsfor runtime layout and edit-surface behavior.packages/designer/src/materials/registry.ts,packages/prop-schemas/src/index.ts, andpackages/designer/src/components/PropertiesPanel.vuefor registration and page behavior property schemas.packages/viewer/src/runtime.ts,packages/viewer/src/render-surface.ts, andpackages/viewer/src/material-registry.tsfor ordinary binding projection, measurement, pagination, repeated overlays, and renderer dispatch.packages/builtin/src/designer.ts,packages/builtin/src/viewer.ts, andpackages/builtin/src/bindings.tsfor internal built-in registration sources;packages/builtin/src/index.ts,packages/builtin/src/all.ts,packages/builtin/src/basic.ts,packages/builtin/src/none.ts,packages/builtin/package.json, andpackages/builtin/tsdown.config.tsfor the public@easyink/builtinexport surface and package-size entry points.packages/shared/src/ai-generation.ts,packages/assistant/designer-bridge/src/material-manifest.ts,packages/assistant/orchestrator/src/prompts.ts, andpackages/assistant/material-knowledge/src/from-manifest.tsfor Assistant material knowledge flow. Do not add material-specific prompt rules to Assistant packages; Assistant uses the live material manifest to build a lightweight Material Router index, then expands only the selected manifest for layout/schema/repair prompts.
Task-specific references:
.github/architecture/05-schema-dsl.md,.github/architecture/06-render-pipeline.md,.github/architecture/08-datasource.md, anddocs/designer/data-binding.mdwhen the material consumes datasource fields, especiallyDataContractBinding..github/architecture/25-ai-assistant.mdanddocs/advanced/contributions.mdwhen the material should be available to Assistant generation or custom-host AI flows.docs/advanced/exporters.mdanddocs/advanced/print-drivers.mdwhen output must be validated through export or print paths.docs/designer/fonts.mdwhen a material exposesfontFamily, page font, text measurement, or print/export output that depends on host-provided fonts.packages/materials/text,packages/materials/rect, andpackages/materials/imagefor simple fixed-size and ordinaryBindingRefpatterns.packages/materials/chart/barfor materialbinding.kind='data-contract', target data model mapping, relation resolver consumption, chart runtime diagnostics, and AI descriptor examples.packages/materials/chart/customandpackages/materials/chart/kernelfor ordinary option binding, trusted JS option source handling, Designer lazy material loading, and full ECharts export boundaries.packages/materials/page-numberfor page-aware repeated overlays.packages/materials/flow-rowfor runtime-height flow/flex behavior.packages/materials/table/dataandpackages/materials/table/kernelfor datasource drop, cell sub-properties, runtime measurement, fragment pagination, and resize side effects.packages/materials/svg/starfor shape-specific deep editing.
Workflow
- Confirm this is a material change: a Schema node, Designer interaction or registration, and Viewer render path are affected.
- Define schema identity first: canonical
TYPE, props interface, defaults, capabilities, andcreateXNode(partial?, unit?). Default nodes must be visible without runtime data. - Keep Schema serializable. Persistent semantics belong in
MaterialNode,node.props,node.binding,node.placement,node.break,node.repeat,node.table, orextensions; runtime plans, DOM refs, preview rows, measurements, loaded fonts, and editing state do not. - Normalize page assumptions. Legal
page.modevalues arefixedandcontinuous, but new behavior should read the owning page strategy field:page.pageModel,page.layout,page.reflow, orpage.pagination. - Keep node geometry semantic.
MaterialNode.x/y/width/heightare document coordinates; Designer projection, measurement, reflow, pagination, and overlay cloning must not silently write runtime output plans back to source schema. - Decide page behavior deliberately. Use
node.placementfor flow/fixed positioning,node.breakforauto-sheetsconstraints, andnode.repeat.scope='every-output-page'or ViewerpageAwareonly for post-pagination element overlays. Repeated/page-aware nodes must not affect flow, document height, or page count. Treatpage.layersas a page-level render-layer boundary, not a material feature hook: use it only for whole-page, non-editable, non-bindable decorations such as text watermarks. Use ordinary elements plusrepeat.scopefor editable headers, footers, logos, page numbers, data-bound repeated content, or editable watermarks. - Implement Designer rendering with
renderContent(nodeSignal, container, renderContextSignal?): render immediately, subscribe tonodeSignal, optionally subscribe to transient render context, escape user-controlled strings or use DOM text APIs, and return deterministic cleanup. - Implement Viewer rendering with
trustedViewerHtml()or anHTMLElement. Ordinary binding results are already projected intocontext.resolvedPropsand the render node's props.data-contractmaterials must callresolveMaterialDataContract(contract, node.binding, context.data ?? {})and report diagnostics. - Add
measure()only when runtime content changes physical size. AddfragmentPaginatoronly when the measured material can split acrossauto-sheets; preservesourceNodeIdand avoid source schema mutation. - If measurement or runtime data owns a dimension, return
MaterialDesignerExtension.resolveControlPolicy()and also guard any deep-edit or behavior path that could mutate the blocked dimension. - Do not register condition capability just to opt into ordinary conditional rendering. Every material defaults to condition support with
removeandreserve; setcondition: falseonly when the material must ignorerenderCondition, or setcondition: { scope: 'node', hiddenEffects: [...] }only to narrow allowed hidden effects. - Register both sides. Built-ins update
packages/builtin/src/designer.ts,packages/builtin/src/viewer.ts,packages/builtin/src/bindings.ts, andpackages/builtin/package.json; then keep the public@easyink/builtinentries aligned. The package only publicly exports the root entry plus./all,./basic,./none, and./package.json:./designer,./viewer, and./bindingsare internal source files, not public subpaths. Root exports should expose the all-set legacy aliases plus explicit all/basic/none bundle aliases and Viewer registration helpers;@easyink/builtin/allexposes every built-in material;@easyink/builtin/basicmust only import the reduced dependency set it registers;@easyink/builtin/nonemust stay empty. Custom hosts register Designer throughruntimeConfig.materials.bundlesorsetupStore, and Viewer throughviewer.registerMaterial(type, binding, extension). - Expose catalog entries deliberately. Built-ins visible in the material panel must appear in
catalogswith a stable groupid, translatable grouplabel, optionalorder, and item entries. Designer registration alone is not panel exposure. A Designer-only material renders[Unknown: type]in Viewer. - Keep heavyweight Designer rendering behind
lazyFactoryonly. Material type, binding definition, prop schemas, locale messages, catalog metadata, default factory, and AI descriptor stay synchronous; Viewer registration stays synchronous. - Add
propSchemasfor simple props-bag fields. Use customreadandcommit,requestPropertyPanel(), orSelectionType.getPropertySchema()when data lives outsidenode.propsor a write touches multiple fields. UseeditorOptions.valueInputfor host/file input; do not storeFile, local paths, file names, picker state, or import state in Schema. - Use shared layout behavior props instead of material-local duplicates.
createLayoutBehaviorPropSchemas()owns placement, break, and repeat UI visibility based on page strategy. - Add deep editing only for meaningful sub-element selection. Define
MaterialGeometry, JSON-safe namespacedSelectionType, behavior middleware, decorations, andtx.run()mutations with stable history labels and merge keys. Inline editors must be selection-scoped. - Put datasource logic at the right layer. Whole-element prop binding uses ordinary
BindingRefplusbinding.primaryProp; table-like internal binding usesbinding.kind='custom',datasourceDrop, and cell-levelbindingorstaticBinding; structured charts usebinding.kind='data-contract'plus target-field mappings innode.binding.kind='data-contract'. - For font-bearing materials, expose a
fontprop schema fornode.props.fontFamilyor the relevant sub-property. Material renderers may emitfont-familyCSS from resolved props, but Designer and Viewer ownFontProvider->FontManager->@font-face. - Add i18n keys for visible labels, tooltips, property labels, reject reasons, history labels, placeholders, and material-local toolbar actions. Prefer
context.t()andstore.t()over hardcoded strings. - Update material-local
src/ai.tswhen Assistant should generate or select the material. Register the descriptor asaiDescriptoron the Designer material entry; Assistant sees it through the live Designer material manifest, routes against lightweight descriptor knowledge first, and only loads detailed usage/schema rules when the material is selected. - Test the smallest useful surface: default factory, Designer repaint/deep behavior, control policy, page behavior props, repeated overlays, fragment pagination, Viewer render/measure, binding projection or data-contract resolution, font-dependent output, registration/catalog fallout, AI manifest, and i18n.
Reference Files
Load only the reference needed for the current task:
references/architecture.md: material system boundaries, current page layout pipeline, Designer/Viewer contracts, and registration.references/development-flow.md: built-in and custom material implementation checklist.references/deep-editing.md: editing session, geometry, selection, behavior, decoration, overlay, inline editor, and resize rules.references/binding-viewer.md: font loading, binding projection, runtime measurement, fragment pagination, page-aware overlays, trusted HTML, export, and print boundaries.references/ai-assistant-materials.md: Assistant manifest,AIMaterialDescriptor.knowledge, Material Router selection, selected-manifest prompt consumers, registry consumers, and custom material AI flow.references/i18n-ai-tests.md: i18n, validation, test rules, and brief AI review reminders.references/case-studies.md: distilled rules fromtable-data,table-kernel,flow-row,svg-star,text, andpage-number.
Hard Rules
- Keep Schema serializable and stable. Do not store DOM nodes, functions, transient selections, virtual preview rows, measured caches, runtime fragments, output pages, or preview-only data in Schema.
- Normalize loose host input with
normalizeDocumentSchema()before relying on required schema fields. - Use
continuous + continuous-paper + stack-flow + flow-y + nonefor continuous paper templates. - Do not branch material behavior solely on
page.mode; read the owning page strategy field instead:page.pageModel,page.layout,page.reflow, orpage.pagination. - Designer and Viewer must both know the material type.
- Do not add
condition: DEFAULT_MATERIAL_CONDITIONor material-local*_CONDITIONconstants for ordinary materials. Conditional rendering is a framework default. Only declarecondition: falseor a narrowedhiddenEffectsoverride when the default is wrong, and test Designer and Viewer behavior for that override. - Built-in materials that should be visible in the material panel must be present in
catalogs; test that catalog entries point to registered materials, the expected panel group includes the new type, and any new catalog label is registered in bundle locale messages. - Use
convertUnit()inside default-node factories when default physical sizes are authored in mm. - Escape all user-controlled strings before HTML interpolation. Viewer HTML must be wrapped with
trustedViewerHtml(). - Use
context.resolvedPropsornode.propsafter Viewer projection; do not hand-resolve ordinarynode.bindinginside material renderers. The exception isDataContractBinding: structured materials consume it throughresolveMaterialDataContract(), not through ad hoccontext.datawalking. - For data-contract materials, contract describes the target data model and binding describes source mappings. Preserve complete
select.pathvalues and let the relation resolver infer shared records or index alignment. binding.formatEditoris a material capability declaration, not Schema data. Openpresetonly when the Viewer/runtime consumes preset formatting for that material; svg/custom and chart-like data-contract materials should generally expose onlycustom.lazyFactoryis Designer-only and should only load the heavyMaterialDesignerExtension. Do not hide material type, binding definition, prop schemas, locale messages, catalog metadata, or AI descriptor behind the lazy chunk.- If a material stores JS source in Schema, store source strings only, document it as trusted template code, convert runtime failures into diagnostics, and do not describe the implementation as a sandbox. Custom ECharts
optionCodeis the current reference. - Use
tx.run()for deep-edit mutations so history, patches, and undo work. UsemergeKeyfor continuous drag/resize edits. - Keep selection payloads JSON-safe and namespaced, such as
table.cellorsvg-star.control. - Bind inline input/editor session meta to the current sub-selection.
- Runtime-height materials must declare a Designer control policy and must not expose any outer or internal path that mutates runtime-owned height.
- Preview-only rows remain outside Schema. If a runtime-height material shows Designer preview rows, keep them display-only.
- Materials may store font family strings in Schema, but must not store font sources,
@font-faceCSS, loaded state, DOM style nodes, or provider results. - Designer font edits must go through the property panel font flow so
ensureFontLoaded()succeeds before the font value is committed to Schema. - Viewer font loading happens before binding, measurement, layout, pagination, and DOM render. Material renderers should only emit
font-familyCSS from resolved props or inherited page font; they should not callFontProviderdirectly. - Material-local toolbars should be compact command toolbars, not identity badges.
- Material Designer UI must not call browser-native confirmation APIs. Destructive host UX belongs behind Designer's interaction bridge or a Contribution-level workflow.
- Repeated/page-aware overlays are post-pagination page overlays. They must not affect flow, document height, page count, output sheets, or source-node editability.
page.layersis a page-level render-layer array, not aMaterialNodeextension point and not a material capability surface. Usepage.layers[]only for whole-page, non-editable, non-bindable decorations such as text watermarks. Use ordinary elements plusrepeat.scopefor editable headers, footers, logos, page numbers, data-bound repeated content, or editable watermarks.- For table-like deep editing, decoration visibility and behavior execution must share the same delegate rules for row/column resize affordances.
- Add or reuse locale keys for anything user-visible in Designer UI, including property labels and history labels.
- Assistant sees material capabilities, binding definitions, data-contract target fields, props, and AI descriptors through the current Designer store manifest. Register custom material
bindingand optionalaiDescriptoron the Designer material entry. - Keep AI descriptors honest: do not list props, binding modes, child support, default sizes, or scenario fitness that the Designer/Viewer implementation cannot satisfy.
- For data-contract materials, set descriptor
binding: 'data-contract', include mapping examples withbinding.kind='data-contract', and explain that relation mode is resolver-derived rather than a UI/schema mode. - Exporters and print drivers must consume Viewer-rendered pages and
ViewerPageMetrics; do not reimplement material layout in those layers.