diff --git a/AGENTS.md b/AGENTS.md index c8eec13dee..2043053ecb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ Currently, we have below features / modules. | [apps](./apps) | - | - | micro sites for Grida | | [library](./library) | [`library/README.md`](./library/README.md) | - | hosted library workers | | [jobs](./jobs) | [`jobs/README.md`](./jobs/README.md) | - | hosted jobs | +| [test](./test) | [`test/README.md`](./test/README.md) | [`test/AGENTS.md`](./test/AGENTS.md) | manual test cases & UX behavior specs | | [.legacy](./.legacy) | - | - | will be removed (fully ignore this directory) | ## Languages, Frameworks, Tools, Infrastructures @@ -171,6 +172,8 @@ For each individual package, refer to the README of its own. We use turborepo (except few isolated packages). +**Manual test cases** for UX behaviors that are impractical to automate live in [`test/`](./test). When fixing a UX bug or implementing interaction-heavy features, check for relevant TCs there and add new ones when appropriate (see [`test/AGENTS.md`](./test/AGENTS.md)). + To run test, build, and dev, use below commands. ```sh diff --git a/editor/AGENTS.md b/editor/AGENTS.md index 2776cc0879..085f719b6a 100644 --- a/editor/AGENTS.md +++ b/editor/AGENTS.md @@ -3,6 +3,7 @@ This package is the Next.js app that powers **`grida.co`** and tenant domains (e.g. `xyz.grida.site`, custom domains). - This doc is a curated “where to change what” map. It’s intentionally **not** exhaustive. +- When fixing UX bugs or changing interaction behavior in `grida-canvas*`, check [`../test/`](../test/) for related manual test cases and add new ones when appropriate. ## Key rules (things that bite later) diff --git a/editor/grida-canvas-react/viewport/ui/surface-text-editor.tsx b/editor/grida-canvas-react/viewport/ui/surface-text-editor.tsx index d00f4af907..67f8fa43a0 100644 --- a/editor/grida-canvas-react/viewport/ui/surface-text-editor.tsx +++ b/editor/grida-canvas-react/viewport/ui/surface-text-editor.tsx @@ -313,8 +313,7 @@ function WasmTextEditorRelay({ node_id }: { node_id: string }) { // Undo/redo: try the WASM session first (word-grouped, IME-aware). // When the session has nothing left, fall through to document-level // undo/redo so the user can undo their way out of content edit mode - // (see ASSERTIONS.md: "Editor History System Takes Precedence in - // Content Edit Mode"). + // (see test/canvas-input-history-undo-cem.md). if (mod && (e.key === "z" || e.key === "Z")) { e.preventDefault(); e.stopPropagation(); diff --git a/editor/grida-canvas/ASSERTIONS.md b/editor/grida-canvas/ASSERTIONS.md deleted file mode 100644 index 25e1442e18..0000000000 --- a/editor/grida-canvas/ASSERTIONS.md +++ /dev/null @@ -1,63 +0,0 @@ -# Grida Canvas - UX Assertions & Test Specifications - -This document serves as a living registry of UX-related assertions and test specifications that require human interaction to verify. These are behaviors that are difficult to test programmatically and where automated testing may bloat the test suite unnecessarily. Core math, algorithmic, or low-level features should be covered by formal unit and integration tests, not documented here. This document is maintained to support faster development cycles while ensuring critical UX behaviors are documented and tracked through manual verification. - -## Purpose - -This document focuses specifically on **UX-related assertions** that are difficult to test programmatically and often require human interaction to verify. Core math, algorithmic, or low-level features should not be documented here—those belong in formal unit and integration test suites. - -**Assertions** listed here represent: - -1. **Troubleshooting-experienced items**: UX features or behaviors that have caused issues in the past and need verification through manual testing. These often require code cleanup or architectural improvements before comprehensive test coverage can be added, but automated testing may not be practical or may bloat the test suite unnecessarily. - -2. **Feature documentation**: UX behaviors that are unlikely to break but document the intended functionality and design decisions. These serve as a reference for what has been built and why, particularly for interactions that require actual usage and human judgment to verify. - -3. **Future test specifications**: Natural language descriptions of how UX features should behave, written in a way that can guide manual testing and user acceptance verification. These are typically interactions that are better verified through human testing rather than automated assertions. - -## Structure - -Each assertion entry is written as a single paragraph that describes the feature as-is and the expected behavior that should be verified. Entries include a date indicating when the assertion was documented or last updated. Entries may optionally include status notes or references to related issues, but the core description should flow naturally without bullet points or structured lists. - -## Usage - -This document is not a replacement for formal test suites, but rather a bridge between development velocity and test coverage. When implementing or refactoring features listed here, use these assertions as a guide for what behaviors must be preserved or improved. - ---- - -## Assertions - -### Vector Resize with Aspect Ratio Preservation (2025-12-08) - -Vector nodes support resizing through edge handles (N/S/E/W) and corner handles (NE/SE/NW/SW). When the SHIFT key is held during resize, the aspect ratio should be preserved uniformly across all handle types. The vector network's vertices and segments must scale proportionally to match the transformed bounding box exactly, ensuring that the visual representation remains consistent with the node's dimensions. Edge handles should maintain aspect ratio by scaling both dimensions based on the dominant movement axis, while diagonal handles should scale uniformly in both dimensions. The vector network transformation must always derive its scale factors from the final bounding box dimensions after the aspect-ratio-preserved transformation has been applied, rather than calculating scales independently from raw movement deltas. This ensures that the vector network geometry always matches the bounding box transformation, regardless of handle type or modifier key combinations. Status: Fixed, ready for comprehensive test coverage. - -### Conditional Resize Handle Z-Order by Zoom Level (2025-12-09) - -When nodes are zoomed out and resize handles appear small on screen, the side handles (north/south/west/east) become more accessible than corner handles, making it easier to resize nodes and double-click on W/E handles for text resize-to-fit functionality. When nodes are zoomed in and handles appear larger, corner handles maintain priority for precise corner manipulation. The system automatically determines handle priority based on how large the node appears on screen (using `MIN_RESIZE_HANDLE_SIZE_FOR_DIAGONAL_PRIORITY_UI_SIZE` as the threshold), ensuring the most relevant handles remain accessible regardless of zoom level. Double-clicking on the side edges (not the corner knobs) triggers resize-to-fit for text nodes. - -### Auto-Layout Direct Container Application (2025-12-09) - -When applying auto-layout (Shift+A) to a single container selection that has no layout applied, the layout is applied directly to that container rather than wrapping it in a new container. This preserves the container's identity and hierarchy, allowing users to convert existing containers to flex layouts without introducing an additional nesting level. The system checks if the selection is exactly one container node and whether that container's layout property is not set to "flex". If both conditions are met, the flex layout properties are applied directly to the selected container, analyzing its children's spatial arrangement to determine optimal flex direction, spacing, and alignment. This behavior is controlled by the `prefersDirectApplication` parameter which defaults to `true`. When `prefersDirectApplication` is `false`, direct application is disabled and nodes are always wrapped in new containers regardless of selection. If the selection contains multiple nodes, a single non-container node, or a container that already has a flex layout, the default behavior applies where selected nodes are wrapped into new flex containers, maintaining backward compatibility with existing workflows. - -### Guideline Event Handling During Canvas Input Modes (2025-12-10) - -Guidelines should not be selectable or handle events when the editor is in eager canvas input mode, specifically when content edit mode (CEM) is active or when the insertion tool is selected. The system maintains an `eager_canvas_input` state that is true when either condition is met: when any content edit mode is active (text, vector, bitmap, etc.) or when the tool type is "insert". When `eager_canvas_input` is true, guidelines must not respond to pointer events, cannot be dragged or focused, and should not trigger gesture creation. This prevents guidelines from interfering with canvas input operations such as inserting new nodes or editing content. The behavior applies to both existing guidelines (dragging to reposition) and new guideline creation from the ruler overlay. Users should be able to interact with guidelines normally when not in these input modes, but the guidelines become passive during content creation and editing workflows to ensure smooth canvas interaction. - -### Resize Handle Visibility Threshold for Small Nodes (2025-12-21) - -When nodes are zoomed out and their viewport size (width or height) falls below the minimum threshold (`MIN_NODE_OVERLAY_RESIZE_HANDLES_VISIBLE_UI_SIZE`), all resize handles are completely hidden to prioritize the translate-drag region. This ensures that thin nodes like text remain easily draggable when zoomed out, as resize handles would otherwise occupy too much of the node's area and interfere with translation gestures. The threshold is calculated based on the physical viewport pixel size, considering both width and height dimensions independently. When either dimension falls below the threshold, resize handles disappear entirely, allowing users to drag nodes smoothly without accidentally triggering resize gestures. This behavior applies to both single node selections and multiple node selection groups, ensuring consistent interaction patterns across all selection types. The resize handles automatically reappear when nodes are zoomed in above the threshold, restoring full resize functionality when handles become large enough to be useful. - -### Editor History System Takes Precedence in Content Edit Mode (2025-12-22) - -When content edit mode is active and the user is typing in a focused input or contentEditable element rendered as part of canvas content (such as the text editor's contentEditable element in text edit mode), the browser's native undo/redo intercepts Cmd+Z and Cmd+Shift+Z before the editor's history system can respond, preventing users from undoing their way out of content edit mode. Without proper handling, users become trapped in the editing state: they cannot use Cmd+Z to undo their way back through the history stack to exit content edit mode, and must instead commit their changes by pressing Escape or clicking outside the input, which rewrites the forward history state and causes the entire relevant history stack to be permanently lost. To prevent this history loss, Cmd+Z and Cmd+Shift+Z must execute the editor's document-level undo/redo system even when a canvas-related input is focused, allowing users to navigate backward through the history stack to exit content edit mode naturally without committing changes that destroy the forward history. The presence of a focused input field within content edit mode should never prevent the editor's history commands from being triggered, ensuring that undo/redo operations affect document-level changes (node properties, transformations, and content modifications) rather than just the input field's own text editing history. This behavior applies specifically to input elements rendered on behalf of canvas content editing workflows, but should not override browser native undo/redo behavior for regular form inputs or UI widget inputs where native browser behavior is expected and appropriate. - -### Container/Frame Title Bar Must Be Above Selection Overlay (2025-12-24) - -When a container or frame is selected and its selection overlay is present, the container/frame title bar must remain interactive and should not be blocked by the selection overlay layer. Users must be able to click the title bar to change selection (including Shift-modified selection) and hover it reliably. This requires the title bar UI layer to render with a higher z-index than the selection overlay layer, while still remaining below resize/rotation handles; the z-order is tracked via the `FLOATING_BAR_Z_INDEX` constant and must remain higher than the selection overlay’s z-index. - -### Grida Clipboard Must Take Priority Over External Clipboard Payloads (2025-12-24) - -Grida's internal clipboard format must take priority over external clipboard payloads because copy/paste is a core trust primitive: users expect the most recent copy action inside Grida to deterministically control what "paste" does, regardless of what was previously copied in other tools. In practice, the OS/browser clipboard is a multi-format, potentially multi-item channel (not a single value), and external tools may place opaque `text/html` payloads with tool-specific markers that can linger, be reordered, or be surfaced unexpectedly by different paste entrypoints. Without an explicit priority invariant, Grida's internal copy/paste can be accidentally "poisoned" by stale external payloads, causing surprising pastes, data loss, and hard-to-reproduce regressions; this assertion exists to keep that invariant documented and manually verifiable during refactors of clipboard encoding/decoding and paste routing. - -### Context Menu Paste Should Respect Cursor Position (2025-12-27) - -When pasting via the context menu (right-click context menu), the pasted content should be inserted at the cursor position where the context menu was triggered, not at the center of the viewport. This ensures that paste via context menu behaves consistently with drag-and-drop operations, where content is placed at the drop location. The system must use the pointer position at the time the context menu was opened to determine where to paste, ensuring users can paste content "here" at their cursor location rather than having it appear at an arbitrary screen center. This behavior applies to all paste operations triggered from the context menu, including Grida clipboard payloads, Figma clipboard payloads, SVG text, images, and plain text content. diff --git a/test/AGENTS.md b/test/AGENTS.md new file mode 100644 index 0000000000..57d5e5b80b --- /dev/null +++ b/test/AGENTS.md @@ -0,0 +1,34 @@ +# `test` + +Manual test cases and UX behavior specifications that are impractical to automate. + +## When to add a new TC + +- You fixed a UX bug that required human interaction to verify — document it here so it doesn't regress silently. +- You implemented a UX behavior with subtle invariants (z-order, modifier keys, pointer events) that would be fragile or verbose as code tests. +- You discovered an edge case during development that only manifests through real user interaction. + +Do **not** add entries for pure logic, math, or data transformations — those belong in co-located `__tests__/` or `*.test.ts` files. + +## How to add + +1. Copy `_template.md` and rename: `{module}-{area}-{short-description}.md` +2. Assign a unique ID: `TC-{MODULE}-{AREA}-{NNN}` (increment NNN within the module+area) +3. Fill in all frontmatter fields — `status` starts as `untested` +4. Write the `## Behavior` section as natural-language prose (design rationale, not just steps) +5. Write `## Steps` with enough detail for someone unfamiliar to verify + +## How to reference from code + +When code implements behavior documented here, add a comment pointing to the file: + +```ts +// (see test/canvas-input-history-undo-cem.md) +``` + +## Rules + +- **One behavior per file.** If a TC covers two independent behaviors, split it. +- **Never delete a TC** — set `status: deprecated` instead (preserves history). +- **When automating**: write the code test, add its path to `covered_by`, set `status: deprecated` once fully covered. +- **Keep prose stable.** Frontmatter fields change freely, but avoid rewriting `## Behavior` unless the actual behavior changed — git blame on that section is the audit trail. diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000000..aa1700a800 --- /dev/null +++ b/test/README.md @@ -0,0 +1,43 @@ +# Evals — Test Case Registry + +This directory contains structured test case (TC) specifications for behaviors that are difficult or impractical to cover with automated tests. These are primarily UX interactions requiring human judgment, visual verification, or complex multi-step browser interaction. + +## Purpose + +- **Regression tracking**: Document behaviors that have broken before and need manual verification after refactors. +- **Feature specification**: Record intended behavior and design decisions as verifiable descriptions. +- **Future test specs**: Natural-language descriptions that can guide manual QA or future automation efforts. + +## What does NOT belong here + +Core math, algorithmic, or low-level features should be covered by formal unit/integration tests co-located with their source code (`__tests__/`, `*.test.ts`, `cargo test`). Only add entries here when automated testing is impractical or would bloat the test suite unnecessarily. + +## File naming + +``` +{module}-{area}-{short-description}.md +``` + +Examples: `canvas-resize-vector-aspect-ratio.md`, `forms-validation-conditional-fields.md` + +## Frontmatter + +Every file uses YAML frontmatter for machine-parseable metadata. See `_template.md` for the full schema. + +Key fields: + +| Field | Description | +| ------------ | -------------------------------------------------------------- | +| `id` | Stable unique ID: `TC-{MODULE}-{AREA}-{NNN}` | +| `module` | Top-level module: `canvas`, `forms`, `database`, `editor`, ... | +| `area` | Sub-domain within the module | +| `status` | `untested` · `verified` · `regression` · `deprecated` | +| `severity` | `low` · `medium` · `high` · `critical` | +| `automatable`| Whether this can eventually become a code test | +| `covered_by` | Paths to test files if partially automated | + +## Workflow + +1. **Adding a new TC**: Copy `_template.md`, rename following the naming convention, fill in frontmatter and body. +2. **After a refactor**: Filter by module/area, manually verify TCs marked `verified` or `regression`. +3. **Promoting to code**: When a TC becomes automatable, write the test, add its path to `covered_by`, and set `status: deprecated` once fully covered. diff --git a/test/_template.md b/test/_template.md new file mode 100644 index 0000000000..e6d7649829 --- /dev/null +++ b/test/_template.md @@ -0,0 +1,27 @@ +--- +id: TC-MODULE-AREA-NNN +title: Short descriptive title +module: canvas +area: resize +tags: [] +status: untested +severity: medium +date: YYYY-MM-DD +updated: YYYY-MM-DD +automatable: false +covered_by: [] +--- + +## Behavior + +Describe the expected behavior in natural language. Focus on *what* should happen and *why* — design rationale, invariants, and edge cases. + +## Steps + +1. Set up the initial state +2. Perform the action +3. Expected: describe the observable outcome + +## Notes + +Optional: history, related issues, links to code, edge cases discovered during testing. diff --git a/test/canvas-clipboard-context-menu-paste-position.md b/test/canvas-clipboard-context-menu-paste-position.md new file mode 100644 index 0000000000..9ea23a580b --- /dev/null +++ b/test/canvas-clipboard-context-menu-paste-position.md @@ -0,0 +1,32 @@ +--- +id: TC-CANVAS-CLIPBOARD-002 +title: Context Menu Paste Should Respect Cursor Position +module: canvas +area: clipboard +tags: [clipboard, paste, context-menu, cursor-position, right-click] +status: verified +severity: high +date: 2025-12-27 +updated: 2025-12-27 +automatable: false +covered_by: [] +--- + +## Behavior + +When pasting via the context menu (right-click), the pasted content should be inserted at the cursor position where the context menu was triggered, not at the center of the viewport. This ensures context menu paste behaves consistently with drag-and-drop, where content is placed at the drop location. + +The system must use the pointer position at the time the context menu was opened to determine paste location. This applies to all paste operations from the context menu: Grida clipboard payloads, Figma clipboard payloads, SVG text, images, and plain text content. + +## Steps + +1. Copy a node (Cmd+C) +2. Right-click at a specific location on the canvas (away from center) +3. Select "Paste" from the context menu +4. Expected: the node appears at the right-click location, not viewport center +5. Repeat with different content types (SVG, image, text) +6. Expected: all paste at the cursor position consistently + +## Notes + +Applies to all clipboard content types routed through context menu paste. diff --git a/test/canvas-clipboard-internal-priority.md b/test/canvas-clipboard-internal-priority.md new file mode 100644 index 0000000000..d5ce583a97 --- /dev/null +++ b/test/canvas-clipboard-internal-priority.md @@ -0,0 +1,34 @@ +--- +id: TC-CANVAS-CLIPBOARD-001 +title: Grida Clipboard Must Take Priority Over External Clipboard Payloads +module: canvas +area: clipboard +tags: [clipboard, copy-paste, priority, external-payload, trust] +status: verified +severity: critical +date: 2025-12-24 +updated: 2025-12-24 +automatable: false +covered_by: [] +--- + +## Behavior + +Grida's internal clipboard format must take priority over external clipboard payloads. Copy/paste is a core trust primitive: users expect the most recent copy action inside Grida to deterministically control what "paste" does, regardless of what was previously copied in other tools. + +The OS/browser clipboard is a multi-format, potentially multi-item channel. External tools may place opaque `text/html` payloads with tool-specific markers that can linger, be reordered, or be surfaced unexpectedly. Without explicit priority, Grida's internal copy/paste can be "poisoned" by stale external payloads, causing surprising pastes and data loss. + +## Steps + +1. Copy a node inside Grida (Cmd+C) +2. Switch to an external app and copy something (e.g. formatted HTML from a browser) +3. Switch back to Grida, do NOT copy anything new inside Grida +4. Paste (Cmd+V) +5. Expected: the external content is pasted (Grida clipboard is stale) +6. Now copy a node inside Grida again (Cmd+C) +7. Paste (Cmd+V) +8. Expected: the Grida node is pasted, not the external content + +## Notes + +This assertion exists to keep the priority invariant documented and manually verifiable during refactors of clipboard encoding/decoding and paste routing. diff --git a/test/canvas-input-guideline-event-blocking.md b/test/canvas-input-guideline-event-blocking.md new file mode 100644 index 0000000000..e618fa2c40 --- /dev/null +++ b/test/canvas-input-guideline-event-blocking.md @@ -0,0 +1,35 @@ +--- +id: TC-CANVAS-INPUT-001 +title: Guideline Event Handling During Canvas Input Modes +module: canvas +area: input +tags: [guidelines, content-edit-mode, insertion-tool, pointer-events, ruler] +status: verified +severity: medium +date: 2025-12-10 +updated: 2025-12-10 +automatable: false +covered_by: [] +--- + +## Behavior + +Guidelines should not be selectable or handle events when the editor is in eager canvas input mode — specifically when content edit mode (CEM) is active or when the insertion tool is selected. + +The system maintains an `eager_canvas_input` state that is true when either condition is met. When true, guidelines must not respond to pointer events, cannot be dragged or focused, and should not trigger gesture creation. This applies to both existing guidelines (drag to reposition) and new guideline creation from the ruler overlay. + +Users should be able to interact with guidelines normally when not in these input modes. + +## Steps + +1. Add a guideline to the canvas (drag from ruler) +2. Enter text edit mode (double-click a text node) +3. Try to drag the guideline — it should not respond +4. Exit text edit mode, select the insert tool +5. Try to drag the guideline — it should not respond +6. Deselect the insert tool (back to pointer) +7. Expected: guideline is now draggable again + +## Notes + +State is tracked via `eager_canvas_input` flag. diff --git a/test/canvas-input-history-undo-cem.md b/test/canvas-input-history-undo-cem.md new file mode 100644 index 0000000000..b392fe3f49 --- /dev/null +++ b/test/canvas-input-history-undo-cem.md @@ -0,0 +1,34 @@ +--- +id: TC-CANVAS-INPUT-002 +title: Editor History System Takes Precedence in Content Edit Mode +module: canvas +area: input +tags: [undo, redo, history, content-edit-mode, cmd-z, contenteditable, text-editing] +status: verified +severity: critical +date: 2025-12-22 +updated: 2025-12-22 +automatable: false +covered_by: [] +--- + +## Behavior + +When content edit mode is active and the user is typing in a focused contentEditable element (e.g. the text editor), the browser's native undo/redo would normally intercept Cmd+Z / Cmd+Shift+Z before the editor's history system can respond. This traps users in the editing state — they cannot undo their way out of content edit mode, and pressing Escape commits changes that destroy forward history. + +To prevent this, Cmd+Z and Cmd+Shift+Z must execute the editor's document-level undo/redo system even when a canvas-related input is focused. This allows navigating backward through the history stack to exit content edit mode naturally without committing changes. + +This behavior applies specifically to input elements rendered on behalf of canvas content editing, but should NOT override browser-native undo/redo for regular form inputs or UI widget inputs. + +## Steps + +1. Double-click a text node to enter text edit mode +2. Type some text +3. Press Cmd+Z repeatedly +4. Expected: editor-level undo fires, eventually exiting content edit mode +5. The forward history stack should be preserved (Cmd+Shift+Z can redo) +6. Verify that Cmd+Z in a regular UI input (e.g. search field) still uses browser-native undo + +## Notes + +Without this fix, users become trapped in CEM and the forward history stack is permanently lost on Escape. diff --git a/test/canvas-input-text-live-update.md b/test/canvas-input-text-live-update.md new file mode 100644 index 0000000000..99ee69058d --- /dev/null +++ b/test/canvas-input-text-live-update.md @@ -0,0 +1,31 @@ +--- +id: TC-CANVAS-INPUT-003 +title: Text Content Must Update Live During Editing +module: canvas +area: input +tags: [text-editing, wasm, content-edit-mode, live-update, regression] +status: verified +severity: critical +date: 2026-03-29 +updated: 2026-03-29 +automatable: false +covered_by: [] +--- + +## Behavior + +When editing text on the canvas, the visible content must update in real time as the user types — not only when the edit is committed or the user exits content edit mode. This applies across all text edit backends, but the regression specifically occurred with the WASM canvas native text edit backend, where typed characters were invisible during the editing session and only rendered upon commit/exit. + +## Steps + +1. Double-click a text node to enter text edit mode +2. Type characters +3. Expected: each character appears on canvas immediately as typed +4. Delete characters +5. Expected: canvas reflects the deletion immediately +6. Press Escape to commit +7. Expected: final text matches what was visible during editing (no content jump) + +## Notes + +Regression was introduced silently and missed — this TC exists as a reminder to verify live text rendering after any text edit backend changes. diff --git a/test/canvas-layout-auto-layout-direct-apply.md b/test/canvas-layout-auto-layout-direct-apply.md new file mode 100644 index 0000000000..1f29f6252e --- /dev/null +++ b/test/canvas-layout-auto-layout-direct-apply.md @@ -0,0 +1,34 @@ +--- +id: TC-CANVAS-LAYOUT-001 +title: Auto-Layout Direct Container Application +module: canvas +area: layout +tags: [auto-layout, flex, shift-a, container, nesting] +status: verified +severity: high +date: 2025-12-09 +updated: 2025-12-09 +automatable: false +covered_by: [] +--- + +## Behavior + +When applying auto-layout (Shift+A) to a single container selection that has no layout applied, the layout is applied directly to that container rather than wrapping it in a new container. This preserves the container's identity and hierarchy. + +The system checks if the selection is exactly one container node whose layout property is not set to "flex". If both conditions are met, flex layout properties are applied directly, analyzing children's spatial arrangement to determine optimal flex direction, spacing, and alignment. This is controlled by `prefersDirectApplication` (defaults to `true`). + +When `prefersDirectApplication` is `false`, or the selection contains multiple nodes, a single non-container node, or a container that already has flex layout, nodes are wrapped into new flex containers. + +## Steps + +1. Create a frame with several child elements (no layout applied) +2. Select the frame and press Shift+A +3. Expected: flex layout is applied directly to the frame, no new wrapper created +4. Verify the frame's identity (name, ID) is preserved +5. Select multiple nodes and press Shift+A +6. Expected: nodes are wrapped in a new flex container + +## Notes + +Parameter `prefersDirectApplication` defaults to `true`. diff --git a/test/canvas-overlay-frame-title-bar-z-order.md b/test/canvas-overlay-frame-title-bar-z-order.md new file mode 100644 index 0000000000..bd50c71cde --- /dev/null +++ b/test/canvas-overlay-frame-title-bar-z-order.md @@ -0,0 +1,31 @@ +--- +id: TC-CANVAS-OVERLAY-001 +title: Container/Frame Title Bar Must Be Above Selection Overlay +module: canvas +area: overlay +tags: [z-order, title-bar, selection-overlay, frame, container, shift-click] +status: verified +severity: high +date: 2025-12-24 +updated: 2025-12-24 +automatable: false +covered_by: [] +--- + +## Behavior + +When a container or frame is selected and its selection overlay is present, the container/frame title bar must remain interactive and not be blocked by the selection overlay layer. Users must be able to click the title bar to change selection (including Shift-modified selection) and hover it reliably. + +The title bar UI layer must render with a higher z-index than the selection overlay layer, while remaining below resize/rotation handles. The z-order is tracked via `FLOATING_BAR_Z_INDEX`. + +## Steps + +1. Select a frame/container so its selection overlay appears +2. Click the frame's title bar +3. Expected: selection changes (or Shift+click toggles selection), not blocked by overlay +4. Hover the title bar — it should respond to hover state +5. Verify resize/rotation handles still render above the title bar + +## Notes + +Z-order constant: `FLOATING_BAR_Z_INDEX`. diff --git a/test/canvas-resize-handle-visibility-threshold.md b/test/canvas-resize-handle-visibility-threshold.md new file mode 100644 index 0000000000..2747904cf4 --- /dev/null +++ b/test/canvas-resize-handle-visibility-threshold.md @@ -0,0 +1,31 @@ +--- +id: TC-CANVAS-RESIZE-003 +title: Resize Handle Visibility Threshold for Small Nodes +module: canvas +area: resize +tags: [zoom, handles, visibility, threshold, translate, drag] +status: verified +severity: medium +date: 2025-12-21 +updated: 2025-12-21 +automatable: false +covered_by: [] +--- + +## Behavior + +When nodes are zoomed out and their viewport size (width or height) falls below `MIN_NODE_OVERLAY_RESIZE_HANDLES_VISIBLE_UI_SIZE`, all resize handles are completely hidden to prioritize the translate-drag region. This ensures thin nodes like text remain easily draggable when zoomed out. + +The threshold is calculated based on physical viewport pixel size, considering width and height independently. When either dimension falls below the threshold, resize handles disappear entirely. This applies to both single node selections and multiple node selection groups. Handles reappear when zoomed in above the threshold. + +## Steps + +1. Select a node and zoom out until it appears very small on screen +2. Observe that resize handles disappear +3. Drag the node — it should translate smoothly without triggering resize +4. Zoom back in until the node is large enough +5. Expected: resize handles reappear + +## Notes + +Threshold constant: `MIN_NODE_OVERLAY_RESIZE_HANDLES_VISIBLE_UI_SIZE`. diff --git a/test/canvas-resize-handle-z-order-zoom.md b/test/canvas-resize-handle-z-order-zoom.md new file mode 100644 index 0000000000..d0f79fc5dc --- /dev/null +++ b/test/canvas-resize-handle-z-order-zoom.md @@ -0,0 +1,32 @@ +--- +id: TC-CANVAS-RESIZE-002 +title: Conditional Resize Handle Z-Order by Zoom Level +module: canvas +area: resize +tags: [zoom, handles, z-order, double-click, text-resize-to-fit] +status: verified +severity: medium +date: 2025-12-09 +updated: 2025-12-09 +automatable: false +covered_by: [] +--- + +## Behavior + +When nodes are zoomed out and resize handles appear small on screen, side handles (N/S/W/E) become higher priority than corner handles, making it easier to resize nodes and double-click on W/E handles for text resize-to-fit. When zoomed in and handles appear larger, corner handles maintain priority for precise corner manipulation. + +The system determines handle priority based on how large the node appears on screen, using `MIN_RESIZE_HANDLE_SIZE_FOR_DIAGONAL_PRIORITY_UI_SIZE` as the threshold. Double-clicking on side edges (not corner knobs) triggers resize-to-fit for text nodes. + +## Steps + +1. Select a node and zoom out until it appears small on screen +2. Hover over the node edges — side handles should take priority over corner handles +3. Zoom in until the node appears large +4. Hover again — corner handles should now take priority +5. With a text node at low zoom, double-click a side edge (W or E) +6. Expected: text resize-to-fit is triggered + +## Notes + +Threshold is controlled by `MIN_RESIZE_HANDLE_SIZE_FOR_DIAGONAL_PRIORITY_UI_SIZE`. diff --git a/test/canvas-resize-vector-aspect-ratio.md b/test/canvas-resize-vector-aspect-ratio.md new file mode 100644 index 0000000000..2f5a4e9454 --- /dev/null +++ b/test/canvas-resize-vector-aspect-ratio.md @@ -0,0 +1,31 @@ +--- +id: TC-CANVAS-RESIZE-001 +title: Vector Resize with Aspect Ratio Preservation +module: canvas +area: resize +tags: [shift-key, vector-network, aspect-ratio, handles, edge-handles, corner-handles] +status: verified +severity: high +date: 2025-12-08 +updated: 2025-12-08 +automatable: false +covered_by: [] +--- + +## Behavior + +Vector nodes support resizing through edge handles (N/S/E/W) and corner handles (NE/SE/NW/SW). When the SHIFT key is held during resize, the aspect ratio should be preserved uniformly across all handle types. The vector network's vertices and segments must scale proportionally to match the transformed bounding box exactly, ensuring that the visual representation remains consistent with the node's dimensions. + +Edge handles should maintain aspect ratio by scaling both dimensions based on the dominant movement axis, while diagonal handles should scale uniformly in both dimensions. The vector network transformation must always derive its scale factors from the final bounding box dimensions after the aspect-ratio-preserved transformation has been applied, rather than calculating scales independently from raw movement deltas. This ensures that the vector network geometry always matches the bounding box transformation, regardless of handle type or modifier key combinations. + +## Steps + +1. Create or select a vector node with a non-square aspect ratio +2. Drag a corner handle (e.g. SE) while holding SHIFT — aspect ratio must be preserved +3. Drag an edge handle (e.g. E) while holding SHIFT — aspect ratio must also be preserved +4. Verify the vector network vertices scale proportionally in both cases +5. Expected: visual geometry matches bounding box exactly, no distortion + +## Notes + +Status: Fixed, ready for comprehensive test coverage.