Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
109 changes: 109 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Copilot Instructions — Weave.js Frontend Showcase

## Repository Overview

This is the **Weave.js Frontend Showcase**, a demo application for [Weave.js](https://github.com/InditexTech/weavejs) — a collaborative canvas framework backed by Konva.js and Yjs. All application code lives in the `/code` subdirectory. The README mentions Next.js, but the actual framework is **TanStack Start** (React + TanStack Router + Vite + Nitro).

## Commands

All commands are run from the `/code` directory:

```bash
npm install # install dependencies
npm run dev # start dev server on https://localhost:3000
npm run build # production build + TypeScript check (vite build + tsc --noEmit)
npm run lint # ESLint
npm run format # Prettier (formats api, app, assets, components, lib, store)
```

Vitest is configured (`vitest.config.mts`) but no tests exist yet. The test environment is jsdom.

## Architecture

### Framework & Routing

- **TanStack Start** (Vite + Nitro) with **TanStack Router** file-based routing
- Routes live in `src/routes/` — `__root.tsx` is the layout root
- API route handlers are in `src/routes/api/`
- The Nitro server proxies all `/weavebff/**` requests to `VITE_BACKEND_ENDPOINT` (the Weave.js backend)

### Weave.js Integration

The core of the app is `<WeaveProvider>` rendered in `components/room/room.tsx`. It receives:
- **renderer**: Konva-based renderer (from `useGetRendererKonvaBase`)
- **store**: Azure Web PubSub store for real-time collaboration (from `useGetAzureWebPubSubProvider`)
- **nodes/plugins/actions**: registered from `components/utils/weave/{nodes,plugins,actions}.ts`

Custom extensions of the Weave.js SDK go in:
- `components/nodes/` — custom canvas node types (e.g. PantoneNode, ColorTokenNode)
- `components/plugins/` — custom canvas plugins
- `components/actions/` — custom canvas tools/actions

### State Management

Two separate state layers:
1. **Zustand** (`useCollaborationRoom` from `@/store/store`) — all UI/room state (sidebar visibility, selected tools, pages, configuration, etc.)
2. **TanStack Query** — server state (rooms list, templates, images, threads, etc.)
3. **`useWeave`** from `@inditextech/weave-react` — Weave.js instance state (canvas status, loaded room, etc.)

### Canvas Room Lifecycle

A "room" is a multi-page collaborative canvas workspace. The room route (`src/routes/rooms/$roomId.tsx`) is wrapped in `<NoSsr>` and `<ClientOnly>` because Weave.js is browser-only. Room initialization flow: load params → fetch connection URL → connect Azure Web PubSub → load room data → render `<WeaveProvider>`.

### Authentication & Session

- Auth uses `better-auth` (`lib/auth.client.ts`)
- Session is accessed via `useGetSession` hook
- The sign-in overlay (`components/sign-overlay/`) is rendered over rooms that require auth

### API Layer

Thin fetch-wrapper functions in `/code/api/` — one file per endpoint, following `{method}-{resource}.ts` naming (e.g. `get-rooms.ts`, `post-image.ts`, `del-template.ts`).

### AI Features

- AI chat panel (`components/room-components/ai-components/chatbot.*.tsx`)
- Vercel AI SDK (`ai`, `@ai-sdk/react`) with Google Gemini and OpenRouter providers
- Server-side AI route handlers in `src/routes/api/ai/`

## Key Conventions

### Path Aliases

`@/*` maps to the `/code` root (e.g. `@/store/store`, `@/lib/utils`, `@/components/room/room`).

### SPDX License Headers

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

### Styling

- Tailwind CSS v4 (via `@tailwindcss/vite` plugin)
- shadcn/ui component library (`components/ui/`) using Radix UI primitives
- Use `cn()` from `@/lib/utils` (clsx + tailwind-merge) for conditional class names

### Commits

Conventional commits enforced by commitlint (`@commitlint/config-conventional`). Husky runs lint on pre-commit and build on pre-push.

### Environment Variables

Env vars are prefixed with `VITE_` (not `NEXT_PUBLIC_`). Copy `.env.example` to `.env` in `/code`:

```
VITE_APP_HOST=https://localhost:3000
VITE_API_ENDPOINT_HUB_NAME=weavejs
VITE_API_ENDPOINT=/weavebff/api/v1
VITE_BACKEND_ENDPOINT=http://localhost:8081
```

Set `DEV_WEAVEJS_REPO_PATH` to a local Weave.js monorepo path to use local SDK sources instead of npm packages (handled in `vite.config.ts` aliases).

### Dependency Overrides

`package.json` overrides pin specific versions of `konva`, `react`, `react-dom`, `yjs`, and `@tanstack/start-plugin-core`. Do not change these without understanding the compatibility constraints with the Weave.js SDK.
2 changes: 2 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ path = [
"code/justfile",
"code/package*.json",
"code/tsconfig.json",
"code/components/ui/**",
"code/components/ai-elements/**"
]
SPDX-FileCopyrightText = "2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)"
SPDX-License-Identifier = "Apache-2.0"
42 changes: 42 additions & 0 deletions code/api/del-room-image-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
//
// SPDX-License-Identifier: Apache-2.0


export const delRoomImageFallback = async (
userId: string,
clientId: string,
roomId: string,
pageId: string,
imageId: string,
relative = true,
) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;
const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT;

const server = `${backendEndpoint}/api/v1`;
const endpoint = `${relative ? apiEndpoint : server}/${hubName}/rooms/${roomId}/pages/${pageId}/image-fallback`;

const response = await fetch(endpoint, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"x-weave-user-id": userId,
"x-weave-client-id": clientId,
},
body: JSON.stringify({
imageId,
}),
});

if (!response.ok) {
throw new Error(
`Error deleting an element from the room image-fallback map: ${response.statusText}`,
);
}

const data = await response.json();

return data;
};
18 changes: 18 additions & 0 deletions code/api/get-room-image-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
//
// SPDX-License-Identifier: Apache-2.0

export const getRoomImageFallback = async (roomId: string, pageId: string) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;

const endpoint = `${apiEndpoint}/${hubName}/rooms/${roomId}/pages/${pageId}/image-fallback`;
const response = await fetch(endpoint);

if (!response.ok && response.status === 404) {
return {};
}

const data = (await response.json()) as Record<string, string>;
return data;
};
4 changes: 3 additions & 1 deletion code/api/get-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

export const getTemplates = async (
roomId: string,
kind?: string,
imageSlots?: number,
offset: number = 0,
limit: number = 20,
) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;

const endpoint = `${apiEndpoint}/${hubName}/rooms/${roomId}/templates?offset=${offset}&limit=${limit}`;
const endpoint = `${apiEndpoint}/${hubName}/rooms/${roomId}/templates?offset=${offset}&limit=${limit}${kind ? `&kind=${kind}` : ""}${imageSlots ? `&imageSlots=${imageSlots}` : ""}`;

Check warning on line 15 in code/api/get-templates.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=InditexTech_weavejs-frontend&issues=AZ5Fmy487oQN5WJKUpTi&open=AZ5Fmy487oQN5WJKUpTi&pullRequest=177

Check warning on line 15 in code/api/get-templates.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not use nested template literals.

See more on https://sonarcloud.io/project/issues?id=InditexTech_weavejs-frontend&issues=AZ5Fmy487oQN5WJKUpTh&open=AZ5Fmy487oQN5WJKUpTh&pullRequest=177

const response = await fetch(endpoint);
const data = await response.json();
Expand Down
21 changes: 19 additions & 2 deletions code/api/pages/post-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,22 @@ export const postPage = async (
name,
thumbnail,
position,
}: { pageId: string; name: string; thumbnail: string; position?: number },
templateId,
target,
}: {
pageId: string;
name: string;
thumbnail: string;
position?: number;
templateId?: string;
target?: {
id: string;
position: {
x: number;
y: number;
};
};
},
) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;
Expand All @@ -26,11 +41,13 @@ export const postPage = async (
name,
thumbnail,
position,
templateId,
target,
}),
});

if (!response.ok) {
throw new Error(`Error creating chat: ${response.statusText}`);
throw new Error(`Error creating page: ${response.statusText}`);
}

const data = await response.json();
Expand Down
43 changes: 43 additions & 0 deletions code/api/post-add-image-template-to-room.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
//
// SPDX-License-Identifier: Apache-2.0

export type PostAddImageTemplateToRoomPayload = {
roomId: string;
pageId: string;
templateId: string;
target: {
id: string;
position: {
x: number;
y: number;
};
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
parameters: Record<string, any>;
};

export const postAddImageTemplateToRoom = async (
payload: PostAddImageTemplateToRoomPayload,
) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;

const endpoint = `${apiEndpoint}/${hubName}/templates/add-image-template-to-room`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: payload ? JSON.stringify(payload) : undefined,
});

if (!response.ok) {
const errorData = await response.json();
throw new Error("Failed to add image template to room: " + errorData.error);
}

const data = await response.json();

return data;
};
14 changes: 9 additions & 5 deletions code/api/post-add-template-to-room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
// SPDX-License-Identifier: Apache-2.0

export type PostAddTemplateToRoomPayload = {
roomId?: string;
roomName?: string;
frameName: string;
templateInstanceId: string;
roomId: string;
pageId: string;
templateId: string;
imagesIds: string[];
target: {
id: string;
position: {
x: number;
y: number;
};
};
};

export const postAddTemplateToRoom = async (
Expand Down
43 changes: 43 additions & 0 deletions code/api/post-room-image-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2025 2025 INDUSTRIA DE DISEÑO TEXTIL S.A. (INDITEX S.A.)
//
// SPDX-License-Identifier: Apache-2.0

export const postRoomImageFallback = async (
userId: string,
clientId: string,
roomId: string,
pageId: string,
imageId: string,
dataURL: string,
relative = true,
) => {
const apiEndpoint = import.meta.env.VITE_API_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;
const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT;

const server = `${backendEndpoint}/api/v1`;
const endpoint = `${relative ? apiEndpoint : server}/${hubName}/rooms/${roomId}/pages/${pageId}/image-fallback`;

const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-weave-user-id": userId,
"x-weave-client-id": clientId,
},
body: JSON.stringify({
imageId,
dataURL,
}),
});

if (!response.ok) {
throw new Error(
`Error adding an element to the room image-fallback map: ${response.statusText}`,
);
}

const data = await response.json();

return data;
};
6 changes: 6 additions & 0 deletions code/api/post-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
export const postTemplate = async ({
roomId,
name,
kind,
imageSlots,
linkedNodeType,
templateImage,
templateData,
}: {
roomId: string;
name: string;
kind: string;
imageSlots?: number;
linkedNodeType: string | null;
templateImage: string;
templateData: string;
Expand All @@ -26,6 +30,8 @@ export const postTemplate = async ({
},
body: JSON.stringify({
name,
kind,
...(imageSlots && { imageSlots }),
linkedNodeType,
templateImage,
templateData,
Expand Down
2 changes: 1 addition & 1 deletion code/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

export type ImageModel =
| "openai/gpt-image-1"
| "gemini/gemini-2.5-flash-image-preview";
| "gemini-3.1-flash-image-preview";
export type ImageQuality = "low" | "medium" | "high";
export type ImageModeration = "low" | "auto";
export type ImageSampleCount = number;
Expand Down
7 changes: 6 additions & 1 deletion code/api/v2/post-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
//
// SPDX-License-Identifier: Apache-2.0

export const postImage = async (roomId: string, file: File) => {
export const postImage = async (
roomId: string,
imageId: string,
file: File,
) => {
const formData = new FormData();
formData.append("file", file);
formData.append("imageId", imageId);

const apiEndpoint = import.meta.env.VITE_API_V2_ENDPOINT;
const hubName = import.meta.env.VITE_API_ENDPOINT_HUB_NAME;
Expand Down
Loading
Loading