Skip to content

[WC-3297] Signature web#2138

Open
gjulivan wants to merge 1 commit into
mainfrom
signature-web
Open

[WC-3297] Signature web#2138
gjulivan wants to merge 1 commit into
mainfrom
signature-web

Conversation

@gjulivan
Copy link
Copy Markdown
Collaborator

@gjulivan gjulivan commented Mar 16, 2026

Signature Widget v2.0 - Custom Widget to Pluggable Widget Migration

Summary

Complete rewrite of the Signature widget, migrating from the legacy Custom Widget architecture (v1.0.8) to the modern
Pluggable Widget API (v2.0.0). This is a major architectural change that brings the widget in line with Mendix's current
standards and provides better Studio Pro integration, improved maintainability, and enhanced functionality.

🚨 Breaking Changes

  • Minimum Mendix version: Now requires Mendix 11.8.0+ (previously 7.13.1)
  • Widget ID changed: com.mendix.widget.custom.signature.Signaturecom.mendix.widget.web.signature.Signature
  • No backward compatibility: Existing implementations using v1.x must be reconfigured in Studio Pro
  • Architecture: Complete rewrite - not an in-place upgrade

✨ What's New

Added Features

  • Custom filename support - New fileName property (textTemplate) for customizing saved file names
  • OnSignEnd action - Event triggers after each stroke with signature image URI as parameter
  • Enhanced dimensions - Min/max height controls, viewport units, overflow options
  • Improved editor preview - Interactive preview in Studio Pro showing grid and dimensions
  • Better validation - Uses @mendix/widget-plugin-component-kit ValidationAlert

Technical Improvements

  • Functional components with hooks - Modern React patterns replacing class-based components
  • Declarative data flow - No manual subscriptions, uses Mendix Pluggable Widget API
  • Better build system - Rollup + @mendix/pluggable-widgets-tools replacing Webpack
  • Enhanced type safety - Full TypeScript support with Mendix types
  • Improved testing - Comprehensive unit test suite

🔄 Migration Notes

For Developers

  • Widget location: packages/pluggableWidgets/signature-web/ (new) vs packages/customWidgets/signature-web/ (legacy)
  • Build commands unchanged: pnpm run build, pnpm run dev, pnpm run test
  • Legacy v1.x remains available in packages/customWidgets/ for reference

For App Builders

  • After upgrade, signature widgets must be reconfigured in Studio Pro
  • All functionality from v1.x is preserved and enhanced
  • No data migration needed - existing signature images remain accessible

📋 Preserved Functionality

All v1.x features are maintained:

  • ✅ Canvas-based signature drawing with signature_pad v5.1.3
  • ✅ Three pen types (fountain, ballpoint, marker)
  • ✅ Customizable pen color
  • ✅ Optional background grid
  • ✅ Flexible sizing options
  • ✅ Read-only mode
  • ✅ Clear signature on attribute change
  • ✅ Resize preserves signature data
  • ✅ Offline capability
  • ✅ Touch and mouse input support

🧪 Testing Completed

  • Unit tests passing (pnpm run test)
  • Linting passing (pnpm run lint)
  • Build successful (pnpm run build)
  • Canvas initialization and drawing
  • Signature save to image attribute
  • Custom filename generation
  • OnSignEnd action execution
  • Resize behavior with signature preservation
  • Read-only mode toggle
  • Clear signature on attribute change
  • Grid rendering with various configurations
  • Studio Pro preview rendering
  • Touch and mouse input

@gjulivan gjulivan requested a review from a team as a code owner March 16, 2026 13:14
@gjulivan gjulivan changed the title Signature web [WC-3297] Signature web Mar 16, 2026
Comment thread packages/pluggableWidgets/signature-web/src/__tests__/AppEvents.spec.tsx Outdated
Comment thread packages/pluggableWidgets/signature-web/src/components/Alert.tsx Outdated
Comment thread packages/pluggableWidgets/signature-web/src/ui/SignaturePreview.css Outdated
Comment thread packages/pluggableWidgets/signature-web/src/assets/Signature.icon.dark.png Outdated
Comment thread packages/pluggableWidgets/signature-web/src/package.xml Outdated
Comment thread packages/pluggableWidgets/signature-web/src/Signature.editorConfig.ts Outdated
Comment thread packages/pluggableWidgets/signature-web/src/Signature.tsx Outdated
Comment thread packages/pluggableWidgets/signature-web/CHANGELOG.md
Comment thread packages/pluggableWidgets/signature-web/package.json Outdated
Comment thread packages/pluggableWidgets/signature-web/package.json
Comment thread packages/pluggableWidgets/signature-web/package.json Outdated
@gjulivan gjulivan force-pushed the signature-web branch 8 times, most recently from 1edd154 to c439717 Compare March 26, 2026 08:45
Comment thread packages/pluggableWidgets/signature-web/README.md Outdated
Comment thread packages/pluggableWidgets/signature-web/package.json Outdated
Comment thread packages/pluggableWidgets/signature-web/package.json Outdated
Comment thread packages/pluggableWidgets/signature-web/mendix-pluggable-widgets-tools.tgz Outdated
Comment thread packages/pluggableWidgets/signature-web/src/Signature.xml Outdated
Comment thread packages/pluggableWidgets/signature-web/src/utils/dimensions.ts
@gjulivan gjulivan force-pushed the signature-web branch 2 times, most recently from 968a371 to 5c70f7e Compare May 5, 2026 09:12
Comment thread packages/pluggableWidgets/signature-web/src/components/SizeContainer.tsx Outdated
Comment thread packages/pluggableWidgets/signature-web/package.json Outdated
Comment thread packages/pluggableWidgets/signature-web/src/Signature.xml
Comment thread packages/pluggableWidgets/signature-web/src/components/Signature.tsx Outdated
@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

Comment thread packages/pluggableWidgets/signature-web/AGENTS.md Outdated
Comment thread packages/pluggableWidgets/signature-web/src/utils/customTypes.ts Outdated
Comment thread packages/pluggableWidgets/signature-web/AGENTS.md Outdated
Comment thread packages/pluggableWidgets/signature-web/AGENTS.md Outdated
@github-actions

This comment was marked as outdated.

@github-actions

This comment was marked as outdated.

@gjulivan gjulivan force-pushed the signature-web branch 2 times, most recently from 387e533 to 9f8b802 Compare May 13, 2026 09:23
@github-actions

This comment was marked as outdated.

@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
packages/pluggableWidgets/signature-web/src/utils/useSignaturePad.ts New hook — SignaturePad initialization, readOnly toggle, resize, clear-on-attribute-change
packages/pluggableWidgets/signature-web/src/components/Signature.tsx New widget component — renders canvas, grid, validation
packages/pluggableWidgets/signature-web/src/Signature.tsx New entry-point — thin wrapper
packages/pluggableWidgets/signature-web/src/Signature.xml New widget manifest
packages/pluggableWidgets/signature-web/typings/SignatureProps.d.ts Generated typings
packages/pluggableWidgets/signature-web/src/Signature.editorConfig.ts Studio Pro design-time config
packages/pluggableWidgets/signature-web/src/Signature.editorPreview.tsx Studio Pro preview component
packages/pluggableWidgets/signature-web/src/components/SizeContainer.tsx Dimension/resize container
packages/pluggableWidgets/signature-web/src/components/Grid.tsx Background grid overlay
packages/pluggableWidgets/signature-web/src/utils/dimensions.ts CSS style helpers
packages/pluggableWidgets/signature-web/src/utils/Utils.ts Base64→File conversion, filename generation
packages/pluggableWidgets/signature-web/src/__tests__/Signature.spec.tsx Unit tests
packages/pluggableWidgets/signature-web/CHANGELOG.md New package changelog
packages/customWidgets/signature-web/** All legacy custom-widget files removed

Skipped (out of scope): dist/, pnpm-lock.yaml, binary image assets, SVG assets


Findings

🚨 High — Stale endStroke listener — stale closure on every render

File: packages/pluggableWidgets/signature-web/src/utils/useSignaturePad.ts line 79 & packages/pluggableWidgets/signature-web/src/components/Signature.tsx line 16

Problem: handleSignEnd in SignatureComponent is a plain arrow function (recreated every render). It is passed as onSignEnd to useSignaturePad, where it becomes a useCallback dep. Because onSignEnd changes every render, handleSignEnd inside the hook also gets a new identity every render. The initialization useEffect lists handleSignEnd as a dep and re-runs, but the isSignatureInitialized.current guard blocks re-registration of the listener. The endStroke listener attached at mount therefore always calls the stale closure from the first render — it will never pick up the latest imageSource, fileName, or onSignEndAction values.

Additionally, the initialization effect has no cleanup, so removeEventListener is never called on unmount (memory / reference leak).

Fix:

// components/Signature.tsx — memoize the callback
const handleSignEnd = useCallback((imageDataUrl?: string): void => {
    if (imageDataUrl) {
        const customFileName = fileName?.value || Utils.generateFileName("signature");
        imageSource.setValue(Utils.convertUrlToBlob(imageDataUrl, customFileName));
    }
    if (onSignEndAction && !onSignEndAction.isExecuting && onSignEndAction.canExecute) {
        onSignEndAction.execute({ signatureImage: imageDataUrl });
    }
}, [imageSource, fileName, onSignEndAction]);
// useSignaturePad.ts — use a stable ref to always call the latest handler, and clean up on unmount
const onSignEndRef = useRef(onSignEnd);
useEffect(() => { onSignEndRef.current = onSignEnd; });

const stableHandleSignEnd = useCallback(() => {
    const imageDataUrl = signaturePadRef.current?.toDataURL();
    if (hasSignatureAttribute) {
        hasSignatureAttribute.setValue(!signaturePadRef.current?.isEmpty());
    }
    if (imageDataUrl) onSignEndRef.current?.(imageDataUrl);
}, [hasSignatureAttribute]);

// initialization effect — remove listener on cleanup
useEffect(() => {
    // ... existing instantiation logic using stableHandleSignEnd ...
    signaturePadRef.current.addEventListener("endStroke", stableHandleSignEnd);
    return () => {
        signaturePadRef.current?.removeEventListener("endStroke", stableHandleSignEnd);
        signaturePadRef.current?.off();
    };
}, [stableHandleSignEnd, penColor, signaturePadOptions, imageSource]);

🔶 Medium — Unit tests use a manual imageSource mock instead of a builder

File: packages/pluggableWidgets/signature-web/src/__tests__/Signature.spec.tsx line 14

Problem: imageSource is constructed as { setValue: jest.fn() } as unknown as SignatureContainerProps["imageSource"], bypassing the status, readOnly, and validation fields that the real EditableImageValue exposes. The tests therefore skip all Mendix data-status edge cases (Loading, Unavailable, ReadOnly) that the widget explicitly handles (e.g. the readOnly branch in useSignaturePad and the validation display in SignatureComponent). This is exactly what the builder pattern in @mendix/widget-plugin-test-utils is designed to prevent.

Fix:

import { EditableValueBuilder } from "@mendix/widget-plugin-test-utils";

// use the appropriate image-value builder or at minimum:
const imageSource = new EditableValueBuilder<string>().withValue("").build() as unknown as SignatureContainerProps["imageSource"];

// add separate test cases:
it("shows validation message when imageSource has a validation error", () => { ... });
it("disables drawing when imageSource is readOnly", () => { ... });

🔶 Medium — Test coverage only exercises the thin entry-point wrapper

File: packages/pluggableWidgets/signature-web/src/__tests__/Signature.spec.tsx

Problem: Both tests mock out SignatureComponent entirely, so they only verify that the eight-line Signature.tsx wrapper passes props through. The actual widget logic — useSignaturePad initialization, handleSignEnd saving to imageSource, onSignEndAction.canExecute guard, hasSignature clear behaviour, resize — has zero test coverage. For a complete rewrite this is a significant gap.

Fix: Add a separate test file (or describe block) for SignatureComponent / useSignaturePad that covers:

  • Signature saved to imageSource.setValue after endStroke fires
  • onSignEndAction called only when canExecute is true
  • Canvas pad disabled (off()) when imageSource.readOnly is true
  • Canvas cleared when hasSignatureAttribute transitions from true to false

⚠️ Low — No Playwright E2E tests for the new pluggable widget

File: packages/pluggableWidgets/signature-web/ (directory)

Note: The old Cypress integration tests were deleted as part of the migration. No replacement Playwright E2E spec (e2e/Signature.spec.js) was added. A minimal smoke test covering canvas visibility and the afterEach session-logout boilerplate should ship with the widget.


⚠️ Low — Utils static class with ESLint suppression

File: packages/pluggableWidgets/signature-web/src/utils/Utils.ts line 1

Note: The file starts with // eslint-disable-next-line @typescript-eslint/no-extraneous-class because all members are static. Prefer plain exported functions, which are tree-shakable and don't require the suppression:

export function convertUrlToBlob(base64Uri: string, fileName: string): File { ... }
export function generateFileName(prefix: string): string { ... }

Positives

  • ActionValue.canExecute is checked before execute() (components/Signature.tsx line 23) — correct Mendix pattern followed
  • hasSignatureAttribute.status === "available" is checked before reading .value in the clear effect — proper status guard
  • All XML property keys are lowerCamelCase and match the generated SignatureProps.d.ts typings exactly
  • editorConfig.ts correctly hides/shows conditional properties (height vs minHeight/maxHeight) based on heightUnit
  • CHANGELOG.md is present and well-structured for a v2.0.0 initial release with breaking-change documentation

Comment on lines +88 to +101
## Commands

```bash
cd packages/pluggableWidgets/signature-web
pnpm run build # Production build
pnpm run dev # Development build with watch
pnpm run start # Start dev server
pnpm run test # Unit tests
pnpm run lint # ESLint
```

## Development

Set `MX_PROJECT_PATH` to your Mendix project, then `pnpm run dev` for hot reload. Changes appear in Studio Pro after sync.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant, it lives in generic docs and true for all widgets.

Comment on lines +103 to +108
## Marketplace

- **App Number:** 107984
- **Minimum Mendix:** 11.8.0
- **Offline Capable:** Yes
- **Platform:** Web only
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant as well, if needed, it can be read out from package.json which is single source of truth

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants