@taylorwong/ichartjs 2.0.12 → 2.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +2 -2
- package/docs/agent/charting-scenario.md +3 -1
- package/docs/agent/coding-agent-integration.md +1 -1
- package/docs/agent/development/2.0-release-readiness.md +4 -0
- package/docs/agent/development/iteration-13.md +278 -0
- package/docs/agent/development/release-sop.md +1 -1
- package/docs/agent/development/roadmap.md +2 -1
- package/docs/agent/diagram-scenario.md +4 -0
- package/docs/agent/editing-contract.md +3 -0
- package/docs/agent/frontend-integration.md +1 -1
- package/docs/agent/project-scenario.md +3 -0
- package/docs/agent/runtime-contract.md +4 -0
- package/docs/agent/usage-scenarios.md +1 -1
- package/docs/agent/zh-CN/charting-scenario.md +3 -1
- package/docs/agent/zh-CN/coding-agent-integration.md +1 -1
- package/docs/agent/zh-CN/diagram-scenario.md +17 -1
- package/docs/agent/zh-CN/editing-contract.md +2 -0
- package/docs/agent/zh-CN/frontend-integration.md +1 -1
- package/docs/agent/zh-CN/project-scenario.md +4 -2
- package/docs/agent/zh-CN/runtime-contract.md +3 -1
- package/docs/agent/zh-CN/usage-scenarios.md +1 -1
- package/docs/manifests/capabilities.json +1310 -32
- package/docs/manifests/commands.json +7 -7
- package/docs/manifests/schemas.json +182 -10
- package/package.json +24 -4
- package/skills/ichartjs/SKILL.md +1 -1
- package/src/capabilities.mjs +10 -36
- package/src/chart-lifecycle.mjs +31 -0
- package/src/charts.mjs +49 -19
- package/src/command.mjs +3 -2
- package/src/contract-registry.mjs +59 -0
- package/src/diagram.mjs +30 -0
- package/src/edit-controller.mjs +1 -1
- package/src/errors.mjs +31 -0
- package/src/index.mjs +142 -10
- package/src/layout.mjs +26 -1
- package/src/project.mjs +45 -16
- package/src/schema.mjs +1 -0
- package/src/spec.mjs +45 -3
- package/types/consumer-fixture.ts +35 -0
- package/types/index.d.ts +33 -3
package/src/spec.mjs
CHANGED
|
@@ -60,6 +60,40 @@ function dependencyErrors(rows) {
|
|
|
60
60
|
return errors;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
function isIsoDate(value) {
|
|
64
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(value)) return false;
|
|
65
|
+
return !Number.isNaN(Date.parse(value));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function projectRowErrors(type, rows) {
|
|
69
|
+
const errors = [], ids = new Map(), requiresId = ['gantt', 'timeline', 'milestone'].includes(type);
|
|
70
|
+
rows.forEach((row, index) => {
|
|
71
|
+
const path = `data.values[${index}]`;
|
|
72
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
73
|
+
errors.push({ code: 'INVALID_PROJECT_ROW', path, message: `${type} rows must be objects.`, suggestion: 'Provide one JSON object per project record.' });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (requiresId && (typeof row.id !== 'string' || !row.id.trim())) errors.push({ code: 'MISSING_RECORD_ID', path: `${path}.id`, message: `${type} rows require a stable string id.`, suggestion: 'Add a unique id to every project record so edits and lineage remain stable.' });
|
|
77
|
+
if (typeof row.id === 'string' && row.id.trim()) {
|
|
78
|
+
if (ids.has(row.id)) errors.push({ code: 'DUPLICATE_RECORD_ID', path: `${path}.id`, message: `Record id ${row.id} is duplicated.`, suggestion: 'Use a unique stable id for every project record.' });
|
|
79
|
+
ids.set(row.id, index);
|
|
80
|
+
}
|
|
81
|
+
if (type === 'gantt') {
|
|
82
|
+
for (const key of ['start', 'end']) {
|
|
83
|
+
if (!isIsoDate(row[key])) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.${key}`, message: `Gantt rows require a valid ISO ${key} date.`, suggestion: `Use ${key}: "2026-09-14" or an explicit ISO timestamp.` });
|
|
84
|
+
}
|
|
85
|
+
if (isIsoDate(row.start) && isIsoDate(row.end) && Date.parse(row.end) < Date.parse(row.start)) errors.push({ code: 'INVALID_INTERVAL', path, message: 'Gantt end must not precede start.', suggestion: 'Set end to the same day or a later ISO date.' });
|
|
86
|
+
} else if (['timeline', 'milestone'].includes(type)) {
|
|
87
|
+
if (!isIsoDate(row.date)) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.date`, message: `${type} rows require a valid ISO date.`, suggestion: 'Use date: "2026-09-14" or an explicit ISO timestamp.' });
|
|
88
|
+
if (typeof (row.title ?? row.name ?? row.label) !== 'string' || !(row.title ?? row.name ?? row.label).trim()) errors.push({ code: 'MISSING_RECORD_LABEL', path: `${path}.title`, message: `${type} rows require a title, name, or label.`, suggestion: 'Add a human-readable title for the project record.' });
|
|
89
|
+
} else if (type === 'burndown') {
|
|
90
|
+
if (!isIsoDate(row.date)) errors.push({ code: 'MISSING_REQUIRED_DATE', path: `${path}.date`, message: 'burndown rows require a valid ISO date.', suggestion: 'Use date: "2026-09-14" or an explicit ISO timestamp.' });
|
|
91
|
+
if (!Number.isFinite(Number(row.remaining)) || Number(row.remaining) < 0) errors.push({ code: 'INVALID_REMAINING', path: `${path}.remaining`, message: 'Burndown remaining must be a non-negative finite number.', suggestion: 'Provide remaining: 0 or a positive numeric value.' });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
return errors;
|
|
95
|
+
}
|
|
96
|
+
|
|
63
97
|
const encodingChannels = {
|
|
64
98
|
line: ['x', 'y'], area: ['x', 'y'], bar: ['x', 'y'], column: ['x', 'y'], scatter: ['x', 'y'],
|
|
65
99
|
pie: ['category', 'value'], funnel: ['category', 'value'], gauge: ['value'], heatmap: ['x', 'y', 'color'], radar: [],
|
|
@@ -188,10 +222,18 @@ export function validateSpec(input = {}) {
|
|
|
188
222
|
const diagramValidation = validateDiagram(diagramInput);
|
|
189
223
|
diagramValidation.errors.forEach(error => errors.push({ ...error, path: error.path || 'diagram' }));
|
|
190
224
|
}
|
|
191
|
-
if (['gantt', 'timeline', 'milestone'].includes(spec.type)) {
|
|
225
|
+
if (['gantt', 'timeline', 'milestone', 'burndown'].includes(spec.type)) {
|
|
192
226
|
const rows = spec.data.values, dateKey = spec.type === 'gantt' ? 'start/end' : 'date';
|
|
193
|
-
if (!rows.some(row => row.start || row.end || row.date)) errors.push({ code: 'INVALID_INTERVALS', path: 'data.values', message: `${spec.type} rows require ${dateKey} dates.`, suggestion: 'Add ISO date values to each row.' });
|
|
194
|
-
|
|
227
|
+
if (!rows.some(row => row && (row.start || row.end || row.date))) errors.push({ code: 'INVALID_INTERVALS', path: 'data.values', message: `${spec.type} rows require ${dateKey} dates.`, suggestion: 'Add ISO date values to each row.' });
|
|
228
|
+
errors.push(...projectRowErrors(spec.type, rows));
|
|
229
|
+
if (spec.type === 'burndown') {
|
|
230
|
+
const seenDates = new Set(), validDates = rows.map(row => row?.date).filter(isIsoDate);
|
|
231
|
+
validDates.forEach((date, index) => {
|
|
232
|
+
if (seenDates.has(date)) warnings.push({ code: 'DUPLICATE_DATE', severity: 'warning', path: 'data.values', message: `Burndown contains duplicate date ${date}; input order is preserved.`, suggestion: 'Aggregate duplicate dates before rendering for one sample per day.' });
|
|
233
|
+
seenDates.add(date);
|
|
234
|
+
if (index && Date.parse(date) < Date.parse(validDates[index - 1])) warnings.push({ code: 'OUT_OF_ORDER_DATE', severity: 'warning', path: 'data.values', message: 'Burndown dates are not sorted; input order is preserved.', suggestion: 'Sort samples by date before creating the chart.' });
|
|
235
|
+
});
|
|
236
|
+
}
|
|
195
237
|
}
|
|
196
238
|
if (spec.type === 'gantt') errors.push(...dependencyErrors(spec.data.values));
|
|
197
239
|
const rows = Array.isArray(spec.data?.values) ? spec.data.values : [];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChartValidationError,
|
|
3
|
+
Chart,
|
|
4
|
+
ChartSpec,
|
|
5
|
+
createChart,
|
|
6
|
+
getCapabilities,
|
|
7
|
+
getBusinessSchema,
|
|
8
|
+
type ChartEvent,
|
|
9
|
+
type EditCommand,
|
|
10
|
+
type LinkedFilters
|
|
11
|
+
} from '@taylorwong/ichartjs';
|
|
12
|
+
|
|
13
|
+
declare const spec: ChartSpec;
|
|
14
|
+
declare const command: EditCommand;
|
|
15
|
+
declare const filters: LinkedFilters;
|
|
16
|
+
|
|
17
|
+
const chart: Chart = createChart({ ...spec, type: spec.type || 'line', data: [{ id: 'row-1', name: 'A', value: 1 }] });
|
|
18
|
+
const listener = (event: ChartEvent) => event.chart.getState().revision;
|
|
19
|
+
chart.on('render', listener).off('render', listener);
|
|
20
|
+
chart.update({ labels: { enabled: true } }).setData([{ id: 'row-2', name: 'B', value: 2 }]);
|
|
21
|
+
chart.setLinkedFilters(filters).setLinkedSelection(['row-2']);
|
|
22
|
+
chart.getDiagramNodes();
|
|
23
|
+
chart.getDiagramEdges();
|
|
24
|
+
chart.getClipboard();
|
|
25
|
+
chart.export({ type: 'json', as: 'object' });
|
|
26
|
+
chart.export({ type: 'svg', as: 'string' });
|
|
27
|
+
chart.export({ type: 'png', as: 'dataurl' });
|
|
28
|
+
chart.export({ type: 'jpeg', as: 'blob' });
|
|
29
|
+
chart.previewEdit(command);
|
|
30
|
+
chart.validateData();
|
|
31
|
+
getCapabilities().contractVersion;
|
|
32
|
+
getBusinessSchema('mindmap-edge');
|
|
33
|
+
try { chart.update({ width: 0 }); } catch (error) { if (!(error instanceof ChartValidationError)) throw error; }
|
|
34
|
+
chart.destroy();
|
|
35
|
+
chart.destroy();
|
package/types/index.d.ts
CHANGED
|
@@ -21,10 +21,11 @@ export interface PreferencesStorage { getItem(key: string): string | null; setIt
|
|
|
21
21
|
export interface StyleRecommendation { version: '1.0'; preset: Exclude<ThemePreset, 'auto'>; mode: ThemeMode; resolvedMode: Exclude<ThemeMode, 'auto'>; palette: Exclude<ThemePalette, 'auto'>; reasons: string[]; warnings: Diagnostic[]; userOverride: boolean; }
|
|
22
22
|
export interface ResolvedTheme extends ThemeConfig { name: string; resolvedMode: Exclude<ThemeMode, 'auto'>; background: string; surface: string; text: string; muted: string; axis: string; grid: string; border: string; focus: string; selection: string; colors: string[]; palettes: Record<string, unknown>; status: Record<string, string>; typography: Record<string, { size: number; weight: number; lineHeight: number; font: string }>; layout: Record<string, unknown>; marks: Record<string, unknown>; reasons: string[]; warnings: Diagnostic[]; }
|
|
23
23
|
export interface Diagnostic { code: string; path?: string; message: string; expected?: unknown; suggestion?: string; [key: string]: unknown; }
|
|
24
|
+
export class ChartValidationError extends Error { name: 'ChartValidationError'; code: 'CHART_VALIDATION_FAILED'; operation: string; details: Diagnostic[]; errors: Diagnostic[]; path?: string; expected?: unknown; received?: unknown; suggestion?: string; toJSON(): Record<string, unknown>; }
|
|
24
25
|
export interface DataFieldInfo { name: string; type: 'quantitative' | 'temporal' | 'category' | 'unknown'; role: 'identifier' | 'measure' | 'temporal-dimension' | 'dimension'; unit: string | null; cardinality: number; validCount: number; nullCount: number; min?: number; max?: number; temporalMin?: string; temporalMax?: string; }
|
|
25
26
|
export interface DataInspection { version: '1.0'; rows: number; fields: DataFieldInfo[]; dimensions: string[]; measures: string[]; temporalFields: string[]; missingValueCount: number; warnings: Diagnostic[]; }
|
|
26
27
|
export interface ChartCapability { type: ChartType; family: string; intents: string[]; required: string[]; optional: string[]; dataShapes: string[]; interactions: string[]; features: Record<string, 'supported' | 'not-applicable' | 'degraded'>; renderers: Array<'canvas' | 'svg'>; exports: string[]; limits: Record<string, number>; }
|
|
27
|
-
export interface RuntimeCapabilities { version: '2.0'; contractVersion: '1.
|
|
28
|
+
export interface RuntimeCapabilities { version: '2.0'; contractVersion: '1.1'; chartTypes: ChartType[]; charts: Record<ChartType, ChartCapability>; intents: string[]; locale?: { default: string; recommended: string[]; appliesTo: string[]; inputDates: string }; renderers: Array<'canvas' | 'svg'>; interactions: string[]; exports: Array<'png' | 'svg' | 'json' | 'jpeg'>; styleSystem: { modes: ThemeMode[]; presets: ThemePreset[]; palettes: ThemePalette[]; switchable: boolean; automatic: boolean; [key: string]: unknown }; preferences?: { version: '1.0'; scopes: string[]; persistence: string[]; fields: string[]; agentAdjustable: boolean; interactiveSettingsUI: boolean; precedence: string[]; schema: PreferenceCapabilities }; headless: { preview?: boolean; json: boolean; svg: boolean; png: boolean | string }; export: { types: string[]; mime: Record<string, string>; browser: Record<string, boolean>; headless: Record<string, string | boolean>; methods: string[]; options: Record<string, unknown>; branding: Record<string, unknown> }; branding: { defaultEnabled: boolean; signature: string; options: Record<string, unknown> }; [key: string]: unknown; }
|
|
28
29
|
export interface ChartPlan { version: '1.0'; intent: string; intentKnown?: boolean; intentSuggestions?: string[]; fallbackUsed?: boolean; primary: ChartType; alternatives: ChartType[]; confidence: number; reasons: string[]; requiredFields: string[]; suggestedEncodings: { dimension: string | null; measure: string | null; secondaryMeasure: string | null }; assumptions: string[]; warnings: Diagnostic[]; unsupportedRequests: string[]; nextActions: string[]; capability: ChartCapability; styleRecommendation: StyleRecommendation; data: DataInspection; }
|
|
29
30
|
export interface AxisState { rawDomain: [number, number]; domain: [number, number]; ticks: number[]; step: number | null; policy: 'nice' | 'raw' | 'explicit'; }
|
|
30
31
|
export interface ChartHealth { version: '1.0'; status: 'ready' | 'degraded' | 'empty'; renderable: boolean; issues: string[]; metrics: { warnings: number; suppressedLabels: number; clampedValues: number; renderedMarks: number }; }
|
|
@@ -40,6 +41,12 @@ export interface ArchitectureNode extends DiagramNode { layerId?: string; bounda
|
|
|
40
41
|
export interface MindmapNode extends DiagramNode { parentId?: string; branch?: string; description?: string; }
|
|
41
42
|
export interface DiagramPoint { x: number; y: number; }
|
|
42
43
|
export interface DiagramEdge { id?: string; from: string; to: string; fromPort?: string; toPort?: string; label?: string; relation?: string; routing?: 'straight' | 'orthogonal' | 'curved'; curveTension?: number; waypoints?: DiagramPoint[]; }
|
|
44
|
+
export interface DiagramElement { id?: string; type?: string; dataRef?: Record<string, unknown>; geometry?: Record<string, unknown>; [key: string]: unknown; }
|
|
45
|
+
export interface ChartEvent { chart: Chart; type?: string; target?: DiagramElement | null; datum?: Record<string, unknown>; selectedData?: Array<Record<string, unknown>>; [key: string]: unknown; }
|
|
46
|
+
export type ChartListener = (event: ChartEvent) => void;
|
|
47
|
+
export interface ChartPlugin { name?: string; install?(chart: Chart): void; beforeRender?(chart: Chart, model: Record<string, unknown>): void; afterRender?(chart: Chart, model: Record<string, unknown>): void; destroy?(chart: Chart): void; }
|
|
48
|
+
export interface ClipboardState { nodes: DiagramNode[]; edges: DiagramEdge[]; }
|
|
49
|
+
export interface ChartState { renderer: string; width: number; height: number; dataCount: number; selected: unknown[]; revision: number; history: { undo: number; redo: number }; view: Record<string, unknown> | null; health: ChartHealth; warnings: Diagnostic[]; preferences: ChartPreferences; clipboard: { nodes: number; edges: number }; [key: string]: unknown; }
|
|
43
50
|
export interface DiagramConfig { mode?: 'process' | 'architecture' | 'mindmap'; layout?: 'manual' | 'layered' | 'tree' | 'radial'; routing?: 'straight' | 'orthogonal' | 'curved'; curveTension?: number; grid?: number; snap?: boolean; }
|
|
44
51
|
export interface ArchitectureLayer { id: string; label?: string; }
|
|
45
52
|
export interface ArchitectureBoundary { id: string; label?: string; nodeIds?: string[]; padding?: number; color?: string; }
|
|
@@ -87,13 +94,35 @@ export interface Chart {
|
|
|
87
94
|
downloadSVG(): ReturnType<Chart['download']>;
|
|
88
95
|
downloadJSON(): ReturnType<Chart['download']>;
|
|
89
96
|
destroy(): void;
|
|
90
|
-
inspectDataSchema(): ReturnType<typeof inspectDataSchema>;
|
|
91
97
|
validateData(): Record<string, unknown>;
|
|
92
98
|
validateEdit(command: EditCommand): Record<string, unknown>;
|
|
93
99
|
previewEdit(command: EditCommand): EditPreview;
|
|
94
100
|
applyEdit(command: EditCommand, options?: { preview?: EditPreview; confirmed?: boolean; approval?: unknown; previewId?: string; expectedRevision?: number; actor?: string; source?: string; reason?: string }): Record<string, unknown>;
|
|
95
101
|
getChangeSet(): Record<string, unknown> | null;
|
|
96
|
-
getState():
|
|
102
|
+
getState(): ChartState;
|
|
103
|
+
on(type: string, listener: ChartListener): this;
|
|
104
|
+
off(type: string, listener: ChartListener): this;
|
|
105
|
+
getAccessibleDescription(): string;
|
|
106
|
+
inspectDataSchema(): Record<string, unknown>;
|
|
107
|
+
toDataTable(): Array<Record<string, unknown>>;
|
|
108
|
+
getSelectedData(): Array<Record<string, unknown>>;
|
|
109
|
+
getElementAt(x: number, y: number): DiagramElement | null;
|
|
110
|
+
select(target: DiagramElement, options?: { additive?: boolean }): this;
|
|
111
|
+
selectBox(start: DiagramPoint, end: DiagramPoint): this;
|
|
112
|
+
getDiagramNodes(): DiagramNode[];
|
|
113
|
+
getDiagramEdges(): DiagramEdge[];
|
|
114
|
+
getDiagramGroups(): Array<Record<string, unknown>>;
|
|
115
|
+
getCollapsedGroupIds(): string[];
|
|
116
|
+
getClipboard(): ClipboardState;
|
|
117
|
+
copySelection(): { valid: boolean; clipboard?: ClipboardState; errors?: Diagnostic[] };
|
|
118
|
+
duplicateSelection(options?: Record<string, unknown>): Record<string, unknown>;
|
|
119
|
+
pasteSelection(options?: Record<string, unknown>): Record<string, unknown>;
|
|
120
|
+
connectNodes(connection: DiagramEdge, options?: Record<string, unknown>): Record<string, unknown>;
|
|
121
|
+
toggleGroupCollapse(groupId: string, options?: Record<string, unknown>): Record<string, unknown>;
|
|
122
|
+
highlight(target: DiagramElement): this;
|
|
123
|
+
use(plugin: ChartPlugin): this;
|
|
124
|
+
/** Advanced JSON-pointer mutation. Prefer update() so the full public contract is explicit. */
|
|
125
|
+
applyPatch(patches: Array<{ op: 'add' | 'replace' | 'remove'; path: string; value?: unknown }>): this;
|
|
97
126
|
getProjectAnalytics(): ProjectAnalyticsState | null;
|
|
98
127
|
getLinkedState(): LinkedState | null;
|
|
99
128
|
setLinkedFilters(filters: LinkedFilters): this;
|
|
@@ -117,6 +146,7 @@ export interface Chart {
|
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
export function createChart(spec: ChartSpec): Chart;
|
|
149
|
+
export const iChart: Record<string, unknown> & { version: string; ChartValidationError: typeof ChartValidationError; createChart: typeof createChart };
|
|
120
150
|
export const defaultPreferences: ChartPreferences;
|
|
121
151
|
export function getPreferenceCapabilities(chartType?: ChartType | string | null, options?: { locale?: 'en' | 'zh-CN' | string }): PreferenceCapabilities;
|
|
122
152
|
export function createPreferencesStore(options?: { storage?: 'localStorage' | 'memory' | PreferencesStorage; storageKey?: string; global?: ChartPreferencesPatch; charts?: Record<string, ChartPreferencesPatch> }): PreferencesStore;
|