Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3ff9d44
chore: initial commit
jesusmpc Jun 3, 2026
99d43ac
feat: refactor image node and tools to avoid saving image fallback da…
jesusmpc Jun 3, 2026
3858cf7
chore: fix code smells
jesusmpc Jun 3, 2026
15a9281
chore: fix code smell
jesusmpc Jun 3, 2026
556adec
chore: improvement on exporting API
jesusmpc Jun 3, 2026
6a9372b
chore: fix code duplications
jesusmpc Jun 3, 2026
a64e98a
chore: refactor missing export area methods
jesusmpc Jun 3, 2026
19623db
chore: refactor missing export area methods
jesusmpc Jun 3, 2026
88ba20e
chore: updated docs
jesusmpc Jun 3, 2026
dcdc542
chore: updated docs
jesusmpc Jun 3, 2026
5bf3b8e
chore: guides fixes
jesusmpc Jun 4, 2026
e69bd01
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 9, 2026
59a7e46
chore: merge
jesusmpc Jun 10, 2026
f26af38
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 10, 2026
a7ec0b3
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 16, 2026
c5ee316
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 16, 2026
a3c07a6
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 17, 2026
7afab78
Merge branch 'main' into improvement/GH-1089-refactor-image-node-and
jesusmpc Jun 17, 2026
b407d5b
feat: new font loading api - improved perf and complexity -, fixes on…
jesusmpc Jun 18, 2026
20c6af4
chore: fix PR issues
jesusmpc Jun 18, 2026
8f56c16
chore: fix sonarqube issues
jesusmpc Jun 18, 2026
90c6fc4
test: expand coverage for image node and custom guides snapping
jesusmpc Jun 18, 2026
e043293
fix: issue with node updates when updated inside a group, release nod…
jesusmpc Jun 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
<!--
SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)

SPDX-License-Identifier: Apache-2.0
-->

# Copilot Instructions for Weave.js

## Project Overview

Weave.js is a collaborative whiteboard/canvas SDK built on [Konva](https://konvajs.org/) (2D canvas rendering) and [Yjs](https://yjs.dev/) (CRDT-based real-time sync). It is a monorepo managed with [Nx](https://nx.dev/) and npm workspaces. All source code lives under `code/`.

## Build, Test, and Lint Commands

All commands run from the `code/` directory.

```bash
# Install dependencies
npm install

# Build all packages
npm run build

# Build a single package
npm run build --workspace=@inditextech/weave-sdk

# Lint all packages
npm run lint

# Lint a single package
npm run lint --workspace=@inditextech/weave-sdk

# Format all packages
npm run format

# Run all tests
npm run test

# Run tests for a single package
npm run test --workspace=@inditextech/weave-sdk

# Run a single test file (from within a package directory)
npx vitest run path/to/file.test.ts

# Type-check the SDK
npm run types:check --workspace=@inditextech/weave-sdk
```

## Repository Structure

```
code/packages/
sdk/ # Core SDK (@inditextech/weave-sdk)
types/ # Shared TypeScript types (@inditextech/weave-types)
react/ # React integration (@inditextech/weave-react)
renderer-konva-base/ # Base Konva renderer
renderer-konva-react-reconciler/ # React-reconciler-based Konva renderer
store-standalone/ # In-memory store (no network)
store-websockets/ # WebSocket-based collaborative store
store-azure-web-pubsub/ # Azure Web PubSub collaborative store
create-backend-app/ # CLI to scaffold a backend test app
create-frontend-app/ # CLI to scaffold a frontend test app
docs/ # Fumadocs-based documentation site (MDX)
```

## Architecture

The `Weave` class (in `sdk/src/weave.ts`) is the central orchestrator. It is constructed with a `WeaveConfig` that wires together the four extension points:

| Extension Point | Base Class | Purpose |
|-----------------|----------------|------------------------------------------------------|
| `WeaveStore` | Abstract class | Yjs sync transport (WebSocket, Azure WS, standalone) |
| `WeaveRenderer` | Abstract class | Konva rendering engine |
| `WeaveNode` | Abstract class | Canvas shape definition |
| `WeaveAction` | Abstract class | Tool behavior (draw, select, move…) |
| `WeavePlugin` | Abstract class | Cross-cutting features (selection, snapping, grid…) |

Internally, `Weave` delegates to a set of **Manager** classes (e.g., `WeaveStageManager`, `WeaveStateManager`, `WeavePluginsManager`) which are not meant to be extended or replaced.

### Data Flow

1. A `WeaveStore` holds a `Y.Doc` with two maps: `weave` (node state) and `weaveMetadata`.
2. On every Yjs `afterTransaction`, subsequent state updates are batched via `requestAnimationFrame`.
3. `onStateChange` triggers `Weave.render()`, which calls `WeaveRenderer.render()`.
4. The renderer diffing maps each state element to a registered `WeaveNode` by `nodeType`, calling `onRender()` for creates and `onUpdate()` for updates.

### Custom Node Pattern

Extend `WeaveNode` and implement at minimum:

```ts
export class MyNode extends WeaveNode {
protected nodeType = 'myNodeType';
initialize = undefined;

onRender(props: WeaveElementAttributes): WeaveElementInstance {
const group = new Konva.Group({ ...props, name: 'node' });
// ... add Konva shapes to group
this.setupDefaultNodeAugmentation(group);
this.setupDefaultNodeEvents(group);
return group;
}

onUpdate(instance: WeaveElementInstance, nextProps: WeaveElementAttributes): void {
instance.setAttrs({ ...nextProps });
// ... update child shapes
}
}
```

Always call `setupDefaultNodeAugmentation()` and `setupDefaultNodeEvents()` when implementing custom nodes.

### Custom Action Pattern

Extend `WeaveAction` and implement `trigger()`:

```ts
export class MyToolAction extends WeaveAction {
protected name = 'myTool';
onPropsChange = undefined;
onInit = undefined;

trigger(cancelAction: () => void, params?: unknown): unknown {
// Set up Konva stage event listeners
}

cleanup(): void {
// Remove event listeners
}
}
```

### Custom Plugin Pattern

Extend `WeavePlugin` and implement lifecycle hooks:

```ts
export class MyPlugin extends WeavePlugin {
protected name = 'myPlugin';

initialize(): void { /* called once at construction */ }
onInit(): void { /* called when Weave initializes */ }
onRender(): void { /* called on each render */ }
enable(): void { this.enabled = true; }
disable(): void { this.enabled = false; }
}
```

### Custom Store Pattern

Extend `WeaveStore` and implement the abstract methods `connect()`, `disconnect()`, `handleAwarenessChange()`, and `setAwarenessInfo()`. The base class handles all Yjs document management and undo/redo.

## Key Conventions

### SPDX License Headers

Every source file must start with:

```ts
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
//
// SPDX-License-Identifier: Apache-2.0
```

### Path Aliases

Within the `sdk` package, `@/` maps to `./src/`. Use `@inditextech/weave-types` for shared types.

### TypeScript

- Strict mode is enabled. Avoid `any`; if unavoidable, use `// eslint-disable-next-line @typescript-eslint/no-explicit-any`.
- `noUnusedLocals` is enforced.
- Use `import type` for type-only imports (`verbatimModuleSyntax` is on).

### Testing

Tests use [Vitest](https://vitest.dev/). Test files follow the `**/*.test.ts` pattern. Not all packages currently have test suites.

### Commit Messages

Follow [Conventional Commits](https://www.conventionalcommits.org/). Enforced by commitlint with `@commitlint/config-conventional`.

### Peer Dependencies: Konva & Yjs

Both `konva` and `yjs` are **peer dependencies** pinned to exact versions (`konva@10.0.2`, `yjs@13.6.27`). When linking packages locally, set these env vars to avoid duplicate instances:

```bash
WEAVE_KONVA_PATH=<repo>/code/node_modules/konva
WEAVE_YJS_PATH=<repo>/code/node_modules/yjs
```

### Dual Entry Points (SDK)

The SDK publishes two entry points:
- `.` — browser/client (`dist/sdk.js`)
- `./server` — Node.js server-side (`dist/sdk.node.js`)

Server-side code lives in `src/backend.ts` and `src/index.node.ts`.

### Nx Caching

Run `npm run reset` (which runs `nx reset`) to clear the Nx computation cache if builds behave unexpectedly.
1 change: 1 addition & 0 deletions code/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- [#1089](https://github.com/InditexTech/weavejs/issues/1089) Refactor Image node and tools to avoid saving image fallback on the state
- [#1090](https://github.com/InditexTech/weavejs/issues/1090) Remove usage of @syncedstore/core
- [#1091](https://github.com/InditexTech/weavejs/issues/1091) Make Store Connection Lifecycle Asynchronous and Increase Azure Web PubSub Frame Size to 512 KB

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ function makeImageNodeHandler() {
return {
getImageSource: vi.fn().mockReturnValue({ width: 400, height: 300 }),
getFallbackImageSource: vi.fn().mockReturnValue(null),
getImageFallbackId: vi.fn().mockReturnValue(undefined),
isImageFallbackEnabled: vi.fn().mockReturnValue(false),
getFallbackImageSourceURL: vi.fn().mockReturnValue(undefined),
create: vi.fn().mockReturnValue({
getAttrs: vi.fn().mockReturnValue({ id: 'test-uuid' }),
}),
Expand Down Expand Up @@ -441,11 +444,11 @@ describe('WeaveImageToolAction', () => {
expect(entry['uploadType']).toBeNull();
});

it('6.3 props.imageFallback/width/height set from image', () => {
it('6.3 props.width/height set from image (imageFallback not propagated to props)', () => {
triggerImageURL();
const entry = ((action as unknown as R)['imageAction'] as R)['test-uuid'] as R;
const props = entry['props'] as R;
expect(props['imageFallback']).toBe(mockImageURL.fallback);
expect(props['imageFallback']).toBeUndefined();
expect(props['width']).toBe(mockImageURL.width);
expect(props['height']).toBe(mockImageURL.height);
});
Expand Down Expand Up @@ -778,7 +781,7 @@ describe('WeaveImageToolAction', () => {
expect(imageNodeHandler.getImageSource).toHaveBeenCalled();
});

it('13.4 uploadType=file → getFallbackImageSource, then loadImageDataURL if null', async () => {
it('13.4 uploadType=file + imageFallbackURL → loadImageDataURL called if imageSource null', async () => {
const imageNodeHandler = makeImageNodeHandler();
imageNodeHandler.getImageSource.mockReturnValue(null);
imageNodeHandler.getFallbackImageSource.mockReturnValue(null);
Expand All @@ -789,7 +792,7 @@ describe('WeaveImageToolAction', () => {
(action as unknown as R)['imageAction'] = {
'test-uuid': makeImageActionEntry({
uploadType: WEAVE_IMAGE_TOOL_UPLOAD_TYPE.FILE,
props: { imageFallback: 'data:image/png;base64,abc', width: 400, height: 300 },
props: { imageFallbackURL: 'data:image/png;base64,abc', width: 400, height: 300 },
}),
};
await (action as unknown as R)['addImageNode']('test-uuid');
Expand Down Expand Up @@ -905,7 +908,7 @@ describe('WeaveImageToolAction', () => {
expect((action as unknown as R)['cancelAction']).toHaveBeenCalled();
});

it('14.2 uploadType=FILE → getFallbackImageSource, loadImageDataURL if null', async () => {
it('14.2 uploadType=FILE + imageFallbackURL → loadImageDataURL called if imageSource null', async () => {
const handler = makeImageNodeHandler();
handler.getImageSource.mockReturnValue(null);
handler.getFallbackImageSource.mockReturnValue(null);
Expand All @@ -916,7 +919,7 @@ describe('WeaveImageToolAction', () => {
(action as unknown as R)['imageAction'] = {
'test-uuid': makeImageActionEntry({
uploadType: WEAVE_IMAGE_TOOL_UPLOAD_TYPE.FILE,
props: { imageFallback: 'data:image/png;base64,abc', width: 0, height: 0 },
props: { imageFallbackURL: 'data:image/png;base64,abc', width: 0, height: 0 },
}),
};
await (action as unknown as R)['handleAdding']('test-uuid');
Expand Down
Loading
Loading