From 9dfde47a323291a567afa6c67730ffb8886a8157 Mon Sep 17 00:00:00 2001 From: andzhm Date: Wed, 8 Apr 2026 23:26:24 +0300 Subject: [PATCH] day_2 ai workshop --- .cursor/commands/add-test-for-component.md | 12 ++ .../create-react-component-with-test.md | 14 +++ .cursor/commands/refactor-code.md | 12 ++ .cursor/commands/security-check.md | 14 +++ .cursor/rules/conventions.mdc | 25 ++++ .cursor/rules/css-conventions.mdc | 25 ++++ .cursor/rules/do-not-touch.mdc | 3 + .cursor/rules/project-architecture.mdc | 24 ++++ .cursor/rules/security-rules.mdc | 30 +++++ .cursor/rules/testing-conventions.mdc | 31 +++++ AGENTS.md | 115 ++++++++++++++++++ .../components/ElementPropertiesPanel.tsx | 68 +++++++++++ packages/excalidraw/components/LayerUI.tsx | 52 ++------ packages/excalidraw/components/LockButton.tsx | 29 +++-- .../excalidraw/components/MegaButton.test.tsx | 11 ++ packages/excalidraw/components/MegaButton.tsx | 30 +++++ 16 files changed, 442 insertions(+), 53 deletions(-) create mode 100644 .cursor/commands/add-test-for-component.md create mode 100644 .cursor/commands/create-react-component-with-test.md create mode 100644 .cursor/commands/refactor-code.md create mode 100644 .cursor/commands/security-check.md create mode 100644 .cursor/rules/conventions.mdc create mode 100644 .cursor/rules/css-conventions.mdc create mode 100644 .cursor/rules/do-not-touch.mdc create mode 100644 .cursor/rules/project-architecture.mdc create mode 100644 .cursor/rules/security-rules.mdc create mode 100644 .cursor/rules/testing-conventions.mdc create mode 100644 AGENTS.md create mode 100644 packages/excalidraw/components/ElementPropertiesPanel.tsx create mode 100644 packages/excalidraw/components/MegaButton.test.tsx create mode 100644 packages/excalidraw/components/MegaButton.tsx diff --git a/.cursor/commands/add-test-for-component.md b/.cursor/commands/add-test-for-component.md new file mode 100644 index 0000000..1fa404a --- /dev/null +++ b/.cursor/commands/add-test-for-component.md @@ -0,0 +1,12 @@ +--- +description: "Add tests for a React component" +--- + +Add or update tests for the React component named $ARGUMENTS: + +1. Locate the component and existing related tests in the same directory. +2. Follow `.cursor/rules/` and `AGENTS.md` conventions before editing. +3. Create or update a colocated `ComponentName.test.tsx` file. +4. Add basic render coverage plus key behavior/state interaction tests. +5. Reuse existing test utilities and patterns already used in this package. +6. Run relevant tests (or provide exact command) and summarize results. diff --git a/.cursor/commands/create-react-component-with-test.md b/.cursor/commands/create-react-component-with-test.md new file mode 100644 index 0000000..a2a4ad7 --- /dev/null +++ b/.cursor/commands/create-react-component-with-test.md @@ -0,0 +1,14 @@ +--- +description: "Generate a new React component with tests" +--- + +Create a React component named $ARGUMENTS: + +1. Create the component file following project conventions +2. Define a TypeScript props interface: `{Name}Props` +3. Use functional component with hooks +4. Use named export +5. Create a colocated test file with basic render test +6. Follow the patterns from existing components in the same directory + +Check .cursor/rules/ for architecture and convention constraints. \ No newline at end of file diff --git a/.cursor/commands/refactor-code.md b/.cursor/commands/refactor-code.md new file mode 100644 index 0000000..a596407 --- /dev/null +++ b/.cursor/commands/refactor-code.md @@ -0,0 +1,12 @@ +--- +description: "Refactoring for a component" +--- + +Refactor a React component named $ARGUMENTS: + +1. Read a files +2. Check .cursor/rules to match it +3. Check for DRY and code smell +4. Propose changes regarding what was finded +5. Check that new code compiles +6. Describe what changes been done \ No newline at end of file diff --git a/.cursor/commands/security-check.md b/.cursor/commands/security-check.md new file mode 100644 index 0000000..89835b7 --- /dev/null +++ b/.cursor/commands/security-check.md @@ -0,0 +1,14 @@ +--- +description: "Run a project security check" +--- + +Perform a security check for this project: + +1. Inspect current changes and identify security-sensitive files. +2. Scan for obvious secrets, unsafe patterns, and risky defaults. +3. Review auth, input handling, and data exposure paths in touched code. +4. Check dependencies and scripts for known risky usage. +5. Report findings by severity with file paths and concrete fixes. +6. If no issues are found, state that clearly and note residual risks. + +Check `.cursor/rules/` and `AGENTS.md` for project guardrails before suggesting changes. diff --git a/.cursor/rules/conventions.mdc b/.cursor/rules/conventions.mdc new file mode 100644 index 0000000..5d2b29e --- /dev/null +++ b/.cursor/rules/conventions.mdc @@ -0,0 +1,25 @@ +--- +description: "Code conventions for components and utilities" +globs: packages/**/*.ts,packages/**/*.tsx +alwaysApply: false +--- + +# Code Conventions + +## Components + +- Functional components + hooks ONLY (no class components) +- Props interface: `{ComponentName}Props` +- Named exports only (no default exports) +- Colocated tests: `ComponentName.test.tsx` + +## TypeScript + +- Strict mode — no `any`, no `@ts-ignore` +- Prefer `type` over `interface` for simple types +- Import types: `import type { X } from "..."` + +## Files + +- kebab-case for files: `element-utils.ts` +- PascalCase for components: `LayerUI.tsx` \ No newline at end of file diff --git a/.cursor/rules/css-conventions.mdc b/.cursor/rules/css-conventions.mdc new file mode 100644 index 0000000..85c6f6d --- /dev/null +++ b/.cursor/rules/css-conventions.mdc @@ -0,0 +1,25 @@ +--- +description: "CSS styling conventions and guardrails" +globs: **/*.css,**/*.scss +alwaysApply: false +--- + +# CSS Conventions + +## Maintainability + +- Keep selectors shallow; avoid deeply nested or overly specific selectors. +- Prefer class selectors over tag selectors for component styling. +- Keep related declarations grouped and ordered consistently. + +## Tokens and Values + +- Prefer CSS variables (design tokens) over hardcoded colors, spacing, and sizes. +- Avoid magic numbers unless they are clearly documented by context. +- Reuse existing utility classes and shared styles before adding new ones. + +## Safety and Scope + +- Avoid `!important` unless there is no safe alternative. +- Do not introduce global resets or broad overrides without explicit need. +- Keep styles scoped to the feature/component to reduce side effects. diff --git a/.cursor/rules/do-not-touch.mdc b/.cursor/rules/do-not-touch.mdc new file mode 100644 index 0000000..3dca909 --- /dev/null +++ b/.cursor/rules/do-not-touch.mdc @@ -0,0 +1,3 @@ +--- +alwaysApply: true +--- diff --git a/.cursor/rules/project-architecture.mdc b/.cursor/rules/project-architecture.mdc new file mode 100644 index 0000000..970757b --- /dev/null +++ b/.cursor/rules/project-architecture.mdc @@ -0,0 +1,24 @@ +--- +description: "Architecture constraints for the project" +globs: packages/** +alwaysApply: false +--- + +# Project Architecture + +## State Management + +- Custom state via actionManager - NOT Redux/Zustand/MobX +- State updates: actionManager.dispatch() ONLY +- State type: AppState (src/types.ts) + +## Rendering + +- Canvas 2D rendering - NOT React DOM for drawing +- Render pipeline: Scene -> renderScene() -> canvas context +- DO NOT use react-konva, fabric.js, pixi.js + +## Dependencies + +- No new npm packages without explicit approval +- Check src/utils/ before adding external helpers diff --git a/.cursor/rules/security-rules.mdc b/.cursor/rules/security-rules.mdc new file mode 100644 index 0000000..1fb2541 --- /dev/null +++ b/.cursor/rules/security-rules.mdc @@ -0,0 +1,30 @@ +--- +description: "Security guardrails for code and reviews" +globs: packages/**/*.ts,packages/**/*.tsx,excalidraw-app/**/*.ts,excalidraw-app/**/*.tsx +alwaysApply: false +--- + +# Security Rules + +## Secrets and Sensitive Data + +- Never hardcode secrets, tokens, passwords, or private keys. +- Do not commit credentials or `.env` values into source files or tests. +- Redact sensitive values in logs, test fixtures, and examples. + +## Input and Output Safety + +- Treat all external/user input as untrusted and validate/sanitize it. +- Avoid unsafe HTML injection patterns; do not bypass sanitization intentionally. +- Validate URLs and external resource identifiers before use. + +## Access and Permissions + +- Follow least-privilege principles when adding new capabilities. +- Do not broaden data access or permissions without explicit justification. + +## Dependencies and Risky Changes + +- No new dependencies without explicit approval. +- Flag security-sensitive behavior changes in PR/summary notes. +- Add tests for security-relevant logic (validation, sanitization, permissions). diff --git a/.cursor/rules/testing-conventions.mdc b/.cursor/rules/testing-conventions.mdc new file mode 100644 index 0000000..4a75684 --- /dev/null +++ b/.cursor/rules/testing-conventions.mdc @@ -0,0 +1,31 @@ +--- +description: "Testing standards for unit and component tests" +globs: **/*.test.ts,**/*.test.tsx +alwaysApply: false +--- + +# Testing Conventions + +## Coverage Expectations + +- Add or update tests for any behavior change. +- Prefer focused tests near the changed code over broad snapshot-only tests. +- Cover happy path and at least one failure/edge path when applicable. + +## Test Quality + +- Use clear test names that describe behavior, not implementation details. +- Assert observable outcomes (rendered output, callbacks, state effects). +- Keep tests deterministic; avoid real timers/network unless explicitly needed. +- Reuse existing test utilities from the package before adding new helpers. + +## Safety + +- Do not use `it.only`/`describe.only` or leave skipped tests unintentionally. +- Avoid overly broad snapshots that hide regressions. +- Keep test data minimal and readable. + +## Agent Verification + +- Run the nearest relevant test file/suite after changes. +- For shared/type-heavy changes, also run lint and typecheck. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fc8af82 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,115 @@ +# AGENTS.md + +This file provides project-specific guidance for coding agents working in this repository. + +## Quick Facts + +- Monorepo name: `excalidraw-monorepo` +- Package manager: `yarn@1.22.22` (use `yarn`, not `npm`) +- Node: `>=18.0.0` +- Main workspace areas: + - `excalidraw-app/` - app shell and app-specific runtime + - `packages/excalidraw/` - core editor package + - `packages/common/`, `packages/element/`, `packages/math/`, `packages/utils/` - shared modules + - `examples/` - integration examples + - `scripts/` - build/release/tooling scripts + +## Most Used Commands + +Run from repo root unless noted otherwise. + +### Setup + +- Install dependencies: `yarn install` +- Fresh reinstall: `yarn clean-install` + +### Development + +- Start app dev server: `yarn start` +- Start production-like app server: `yarn start:production` +- Build app: `yarn build` +- Build all packages: `yarn build:packages` +- Build only core package: `yarn build:excalidraw` + +### Testing and Quality + +- Main test run: `yarn test` +- Full CI-like checks: `yarn test:all` +- Unit/integration tests: `yarn test:app` +- Typecheck: `yarn test:typecheck` +- Lint: `yarn test:code` +- Format check: `yarn test:other` +- Coverage: `yarn test:coverage` +- Update snapshots: `yarn test:update` +- Auto-fix lint and format: `yarn fix` + +### Cleanup and Maintenance + +- Remove build artifacts: `yarn rm:build` +- Remove `node_modules`: `yarn rm:node_modules` + +## Code Conventions + +- TypeScript strict mode; avoid `any` and `@ts-ignore`. +- Use functional React components and hooks. +- Use named exports (no default exports). +- Name props as `{ComponentName}Props` for components. +- Keep tests colocated where practical (`ComponentName.test.tsx`). +- Use `import type { ... }` for type-only imports. + +## Architecture Rules + +- State management uses internal action flow: + - Use `actionManager.dispatch()` for state updates. + - Do not introduce Redux, Zustand, MobX, or similar global stores. +- Rendering architecture: + - Canvas rendering pipeline is authoritative for drawing. + - Do not replace drawing flow with React DOM-based drawing. + - Do not add canvas abstraction libraries (react-konva, fabric.js, pixi.js). + +## Guardrails (Must Follow) + +- Do not add new npm/yarn dependencies without explicit approval. +- Do not modify protected core files unless explicitly requested and validated: + - `src/core/renderer.ts` + - `src/data/restore.ts` + - `src/actions/manager.ts` + - `src/types.ts` +- If protected files are changed, run complete tests and perform manual verification. + +## Agent Workflow Expectations + +When implementing code changes: + +1. Read relevant files and existing local patterns first. +2. Follow `.cursor/rules/*.mdc` and this file before editing. +3. Keep changes focused and avoid unrelated refactors. +4. Reuse existing utilities before introducing new abstractions. +5. After edits, run targeted checks first, then broader checks if needed. +6. Report what changed, why, and any residual risks. + +## Testing Strategy for Agents + +- For small isolated component changes: + - Run targeted tests first (nearest suite/file). + - Run lint/typecheck if types or shared interfaces changed. +- For broader/editor-impacting changes: + - Run `yarn test:all` when feasible. +- Minimum expected verification for most PR-sized changes: + - `yarn test:code` + - `yarn test:typecheck` + - Relevant `yarn test` coverage for touched areas + +## Pull Request and Review Notes + +- Keep PRs scoped and descriptive. +- Include rationale, not only a list of file changes. +- Flag behavior changes, migration implications, and follow-up tasks. +- Prefer adding or updating tests alongside behavior changes. + +## Security and Reliability Basics + +- Never commit secrets, credentials, or private tokens. +- Validate and sanitize untrusted inputs where relevant. +- Avoid risky defaults that can leak data or broaden permissions. +- Call out security-sensitive changes explicitly in summaries. \ No newline at end of file diff --git a/packages/excalidraw/components/ElementPropertiesPanel.tsx b/packages/excalidraw/components/ElementPropertiesPanel.tsx new file mode 100644 index 0000000..6f29fb6 --- /dev/null +++ b/packages/excalidraw/components/ElementPropertiesPanel.tsx @@ -0,0 +1,68 @@ +import clsx from "clsx"; + +import { CLASSES } from "@excalidraw/common"; + +import type { + NonDeletedElementsMap, + NonDeletedSceneElementsMap, +} from "@excalidraw/element/types"; + +import { CompactShapeActions, SelectedShapeActions } from "./Actions"; +import { Island } from "./Island"; +import { useStylesPanelMode } from "./App"; + +import type { ActionManager } from "../actions/manager"; +import type { AppClassProperties, AppState, UIAppState } from "../types"; + +type ElementPropertiesPanelProps = { + appState: UIAppState; + elementsMap: NonDeletedElementsMap | NonDeletedSceneElementsMap; + renderAction: ActionManager["renderAction"]; + app: AppClassProperties; + setAppState: React.Component["setState"]; + maxHeight: number; +}; + +export const ElementPropertiesPanel = ({ + appState, + elementsMap, + renderAction, + app, + setAppState, + maxHeight, +}: ElementPropertiesPanelProps) => { + const isCompactStylesPanel = useStylesPanelMode() === "compact"; + + if (isCompactStylesPanel) { + return ( + + + + ); + } + + return ( + + + + ); +}; diff --git a/packages/excalidraw/components/LayerUI.tsx b/packages/excalidraw/components/LayerUI.tsx index bf774bf..ea9f465 100644 --- a/packages/excalidraw/components/LayerUI.tsx +++ b/packages/excalidraw/components/LayerUI.tsx @@ -28,9 +28,7 @@ import { t } from "../i18n"; import { calculateScrollCenter } from "../scene"; import { - SelectedShapeActions, ShapesSwitcher, - CompactShapeActions, } from "./Actions"; import { LoadingMessage } from "./LoadingMessage"; import { LockButton } from "./LockButton"; @@ -61,6 +59,7 @@ import { Island } from "./Island"; import { JSONExportDialog } from "./JSONExportDialog"; import { LaserPointerButton } from "./LaserPointerButton"; import { Toast } from "./Toast"; +import { ElementPropertiesPanel } from "./ElementPropertiesPanel"; import "./LayerUI.scss"; import "./Toolbar.scss"; @@ -160,8 +159,7 @@ const LayerUI = ({ generateLinkForSelection, }: LayerUIProps) => { const editorInterface = useEditorInterface(); - const stylesPanelMode = useStylesPanelMode(); - const isCompactStylesPanel = stylesPanelMode === "compact"; + const isCompactStylesPanel = useStylesPanelMode() === "compact"; const tunnels = useInitializeTunnels(); const spacing = isCompactStylesPanel @@ -235,8 +233,6 @@ const LayerUI = ({ ); const renderSelectedShapeActions = () => { - const isCompactMode = isCompactStylesPanel; - return (
- {isCompactMode ? ( - - - - ) : ( - - - - )} +
); }; diff --git a/packages/excalidraw/components/LockButton.tsx b/packages/excalidraw/components/LockButton.tsx index 8363ffa..46e7834 100644 --- a/packages/excalidraw/components/LockButton.tsx +++ b/packages/excalidraw/components/LockButton.tsx @@ -6,7 +6,7 @@ import { LockedIcon, UnlockedIcon } from "./icons"; import type { ToolButtonSize } from "./ToolButton"; -type LockIconProps = { +type LockButtonProps = { title?: string; name?: string; checked: boolean; @@ -21,30 +21,37 @@ const ICONS = { UNCHECKED: UnlockedIcon, }; -export const LockButton = (props: LockIconProps) => { +export const LockButton = ({ + title, + name, + checked, + onChange, + isMobile, +}: LockButtonProps) => { + const labelTitle = title ? `${title} — Q` : "Q"; + const icon = checked ? ICONS.CHECKED : ICONS.UNCHECKED; + return ( ); }; diff --git a/packages/excalidraw/components/MegaButton.test.tsx b/packages/excalidraw/components/MegaButton.test.tsx new file mode 100644 index 0000000..5dfd471 --- /dev/null +++ b/packages/excalidraw/components/MegaButton.test.tsx @@ -0,0 +1,11 @@ +import { render, screen } from "@testing-library/react"; + +import { MegaButton } from "./MegaButton"; + +describe("MegaButton", () => { + it("renders the provided label", () => { + render(); + + expect(screen.getByRole("button", { name: "Launch" })).toBeTruthy(); + }); +}); diff --git a/packages/excalidraw/components/MegaButton.tsx b/packages/excalidraw/components/MegaButton.tsx new file mode 100644 index 0000000..5543b8c --- /dev/null +++ b/packages/excalidraw/components/MegaButton.tsx @@ -0,0 +1,30 @@ +import React, { useCallback } from "react"; + +export interface MegaButtonProps { + label: string; + onSelect?: () => void; + disabled?: boolean; + className?: string; +} + +export const MegaButton = ({ + label, + onSelect, + disabled = false, + className = "", +}: MegaButtonProps) => { + const handleClick = useCallback(() => { + onSelect?.(); + }, [onSelect]); + + return ( + + ); +};