Serenity 10.3.5 Release Notes (2026-07-25)
These release notes detail the significant changes made in Serenity and StartSharp from version 10.3.0 to 10.3.5. For a complete list of changes, please refer to the Serenity Change Log.
EntityDialog Methods Now Return Promise (Breaking Change)
The EntityDialog methods save(), loadById(), load(), delete(), and undelete() have been updated to return a PromiseLike<T> instead of void. This is a breaking change if you have any code that overrides these methods and does not return a promise.
// Before (10.3.0 and earlier):
protected save(callback?: (response: SaveResponse) => void, initiator?: SaveInitiator): void | boolean {
return ValidationHelper.submit(this.byId('Form'),
() => this.validateBeforeSave(),
() => this.save_submitHandler(callback, initiator));
}
protected saveHandler(options: ServiceOptions<SaveResponse>, callback: (response: SaveResponse) => void, initiator: SaveInitiator): void {
serviceCall(options);
}
protected loadById(id: any, callback?: (response: RetrieveResponse<TItem>) => void, fail?: () => void): void {
this.loadByIdHandler(this.getLoadByIdOptions(id, callback), callback, fail);
}
protected loadByIdHandler(options: ServiceOptions<RetrieveResponse<TItem>>, callback: (response: RetrieveResponse<TItem>) => void, fail: () => void): void {
const request = serviceCall(options);
fail && ((request as any)?.fail ? (request as any).fail(fail) : request.then(null, fail));
}
// After (10.3.5):
protected save(callback?: (response: SaveResponse) => void, initiator?: SaveInitiator): PromiseLike<SaveResponse> | false {
if (!ValidationHelper.submit(this.byId('Form'), () => this.validateBeforeSave(), null))
return false;
return this.save_submitHandler(callback, initiator);
}
protected saveHandler(options: ServiceOptions<SaveResponse>, callback: (response: SaveResponse) => void, initiator: SaveInitiator): PromiseLike<SaveResponse> {
return serviceCall(options);
}
protected loadById(id: any, callback?: (response: RetrieveResponse<TItem>) => void, fail?: () => void): PromiseLike<RetrieveResponse<TItem>> {
return this.loadByIdHandler(this.getLoadByIdOptions(id, callback), callback, fail);
}
protected loadByIdHandler(options: ServiceOptions<RetrieveResponse<TItem>>, callback: (response: RetrieveResponse<TItem>) => void, fail: () => void): PromiseLike<RetrieveResponse<TItem>> {
const response = serviceCall<RetrieveResponse<TItem>>(options);
fail && ((response as any)?.fail ? (response as any).fail(fail) : response.then(null, fail));
return response;
}
Additionally, new methods like getDeleteRequest() and getUndeleteRequest() have been introduced to better decouple request creation from the handler logic:
protected getDeleteRequest(): DeleteRequest {
return { EntityId: this.entityId };
}
protected getUndeleteRequest(): UndeleteRequest {
return { EntityId: this.entityId };
}
The IEditDialog interface has also been updated accordingly:
export interface IEditDialog {
load(entityOrId: any, done: () => void, fail?: (p1: any) => void): PromiseLike<RetrieveResponse<any>>;
}
Migration Guide
If you override any of these methods in your derived dialog classes, you need to update the return type and ensure a promise is returned:
// If you override save():
protected override save(callback?: (response: SaveResponse) => void,
initiator?: SaveInitiator): PromiseLike<SaveResponse> | false {
// Your custom logic
return super.save(callback, initiator); // must return the promise
}
// If you override loadById():
protected override loadById(id: any, callback?: (response: RetrieveResponse<TItem>) => void,
fail?: () => void): PromiseLike<RetrieveResponse<TItem>> {
// Your custom logic
return super.loadById(id, callback, fail); // must return the promise
}
TypeScript 7 Migration
The project has been migrated to TypeScript 7.0.2. This builds on the TypeScript 6 migration from 10.3.0 and brings the latest TypeScript features. Key points:
dts-bundle-generatorstill uses TypeScript 6 as TS7 does not have the required API yet- Various code adjustments were needed for Vite 8 / rolldown / oxc compatibility:
Routernamespace converted to a class (oxc does not supportexport letin namespaces)Enumnamespace converted toconst(same reason)SyntaxKindis now passed via constructor in TypeScript AST node classes instead of being set after construction
// tsconfig.json additions for TS7 (same as TS6):
{
"compilerOptions": {
"strict": false,
"noUncheckedSideEffectImports": false,
"noImplicitOverride": true,
"rootDir": "."
}
}
Domwise Fixes (10.3.5)
Several minor bug fixes have been made in the domwise library:
SVG Presentation Attributes
Fixed a bug where SVG presentation attributes were never removed on re-assignment because removeAttributeNS was called with swapped arguments. This affected SVG elements where attributes like fill, stroke, etc. were being updated.
// Before (broken):
element.removeAttributeNS(attrName, namespaceURI); // arguments swapped
// After (fixed):
element.removeAttributeNS(namespaceURI, attrName);
<select> Value with Signals
Fixed an issue where <select> element values were broken when using signals. The assignProp function now properly handles HTMLSelectElement (both single and multi-select), and the factory correctly unwraps signal values before assigning.
Show Component Memory Leak
Fixed a memory leak in the Show component where lifecycleNode was being set to non-EventTarget values (e.g., string children). This caused event listeners to never be cleaned up.
derivedSignal Fallback Crash
Fixed a crash in derivedSignal when the fallback value is used and subscribe does not fire synchronously on the first call.
Other Bug Fixes
- unsignalizePrevClass: Now recursively handles nested arrays
- Spellcheck cast: Fixed incorrect cast from
HTMLInputElementtoHTMLElement - getContent parameter type: Fixed parameter type in
Showcomponent
Custom Component Overload for jsx Function
The custom component overload for the jsx function has been restored. This allows TypeScript JSX transpilation to properly type-check custom components without needing a separate factory:
// The overload now accepts ComponentType:
export function jsx<P extends {}, TElement extends JSXElement = JSXElement>(
type: ComponentType<P, TElement>,
props?: P & { children?: ComponentChildren; ref?: Ref<TElement> } | null,
): TElement
This was previously removed and is now restored for compatibility with TypeScript 7 and custom component patterns.
Auto-Registration Attributes (C#)
Three new attributes have been introduced that allow an implementation type to be auto-registered for its implemented interfaces:
[RegisterScoped]— Registers as scoped lifetime[RegisterSingleton]— Registers as singleton lifetime[RegisterTransient]— Registers as transient lifetime
// Auto-register MyRepository for all its interfaces:
[RegisterScoped]
public class MyRepository : IMyRepository, IMyService
{
// ...
}
// Register as singleton:
[RegisterSingleton]
public class MyCache : IMyCache { }
// Register with a specific service type (not all interfaces):
[RegisterSingleton(typeof(IMyRepository))]
public class MyRepository : IMyRepository, IMyService { }
IRequestHandler Exclusion
Request handlers (interfaces deriving from IRequestHandler) are excluded from auto-registration to avoid conflicts with the standard handler registration pipeline:
// This will NOT auto-register IRequestHandler implementations:
public interface IRequestHandler { }
public interface IRequestHandler<TRow> : IRequestHandler { }
Configuration
To enable auto-registration, add this line as the last line in your ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// ... your other registrations ...
services.AddAutoRegisteredServices(); // should be the last line
}
Additional Options
| Option | Type | Default | Description |
|---|---|---|---|
ReplaceExisting |
bool |
false |
If true, replaces any existing registration for the same service type |
AsSelf |
bool |
false |
If true, also registers the type as itself in addition to interface registrations |
Order |
int |
0 |
Controls the order of registration when multiple implementations exist |
// Replace an existing registration:
[RegisterSingleton(ReplaceExisting = true)]
public class CustomImplementation : ISomeService { }
// Register both as interface and as self:
[RegisterSingleton(AsSelf = true)]
public class MyService : IMyService { }
GridEditController Updates (StartSharp)
Several enhancements have been made to the GridEditController:
useGridEditorSave Renamed to useGridEditorServices
The option useGridEditorSave has been renamed to useGridEditorServices to better reflect that it controls both save and delete operations:
// Before:
new GridEditController({
grid: myGrid,
useGridEditorSave: true
});
// After:
new GridEditController({
grid: myGrid,
useGridEditorServices: true
});
New Options
new GridEditController({
grid: myGrid,
// Read-only mode check:
isReadOnly: false, // or rely on grid.get_readOnly()
// Permission checks:
hasInsertPermission: () => myGrid.hasInsertPermission(),
hasUpdatePermission: () => myGrid.hasUpdatePermission(),
canEditItem: (item) => someCondition,
// Delete permission (uses grid method if available):
hasDeletePermission: () => myGrid.hasDeletePermission(),
});
isReadOnly: If not specified, checks the grid'sget_readOnly()method orreadOnlypropertyhasInsertPermission,hasUpdatePermission,canEditItem: Fine-grained control over edit operationshasDeletePermission: Uses the grid'shasDeletePermission()method if available, falls back to the option
Auto-Register Function
The GridEditController now has an enableAutoRegister function for automatic registration:
// Auto-register GridEditController for all grids matching the pattern
GridEditController.enableAutoRegister(myGrid, options);
Async Operations
The controller's action handlers are now properly async, using await on save and delete operations:
protected async gridEditActionClick(e: MouseEvent) {
// ...
await this.saveChanges({ autoSavingItem: item });
// ...
await this.deleteRow(row);
// ...
}
EntityGridDialog Missing Methods
The EntityGridDialog class (which mirrors EntityDialog for grid-based editing) was missing several methods that exist in EntityDialog. These have been added:
// Added methods:
protected async commitEdits(): Promise<boolean>
protected getDeleteOptions(callback): ServiceOptions<DeleteResponse>
protected getDeleteServiceMethod()
protected getUndeleteServiceMethod()
protected async retrieveLocalizations(): Promise<Record<string, Partial<TItem>>>
A new test was also added to catch any future missing EntityDialog prototype functions in EntityGridDialog, ensuring they stay in sync.
CSP Compatibility: Removal of javascript: URLs
All javascript: URLs in links have been removed as they are incompatible with strict Content Security Policy (CSP). They have been replaced with role="button" on anchor elements or other appropriate patterns:
<!-- Before (incompatible with strict CSP): -->
<a href="javascript:doSomething()">Click</a>
<a href="javascript:;" onclick="doSomething()">Click</a>
<!-- After (CSP-compatible): -->
<a role="button" onclick="doSomething()">Click</a>
Combo/Lookup Editor dialogType as Type or Promise
The dialogType option of combo/lookup editors now accepts a type reference or a promise in addition to a string:
// Before (string only):
new ComboboxEditor({
dialogType: "MyModule.MyDialog"
});
// After (also accepts type or promise):
import { MyDialog } from "./MyDialog";
new ComboboxEditor({
dialogType: MyDialog // direct type reference
});
// Or with lazy loading:
new ComboboxEditor({
dialogType: () => import("./MyDialog").then(m => m.MyDialog)
});
New createUploadInput Helper
A new createUploadInput helper function has been exposed, which creates a file upload input element and returns the underlying uploader object for more control:
import { createUploadInput } from "@serenity-is/corelib";
const { uploader, input } = createUploadInput({
uploadUrl: "/File/Upload",
inputClass: "my-upload-input",
done: (response) => {
console.log("Upload complete:", response);
}
});
Header Filters Fixes
getFilterValue Null Check (10.3.3)
Ensured HeaderFiltersPlugin.getFilterValue does not fail when the value is null or undefined:
// Before: could throw when value is null/undefined
// After: safely handles null/undefined values
Counter Text Cleanup
Removed \n and \t characters from <span class="counter"/> texts (which are used in login/2FA) to ensure display text is shown properly even when such characters are present.
Translation Validation (10.3.2)
The language ID is now validated before saving translations. Only valid ISO language codes (e.g., en-GB, tr-TR) are accepted:
// Invalid language IDs (e.g., typos, non-ISO codes) are rejected
// with an appropriate error message instead of silently failing.
Build/Tooling Changes
node --run Usage
The project now uses node --run scriptname instead of pnpm scriptname or npm run scriptname for running scripts defined in package.json. This is faster as it avoids the package manager overhead:
# Before:
pnpm build
# After:
node --run build
Vite 8 Migration
The build system has been migrated to Vite 8 (from Vite 5/6), which uses rolldown as the bundler and oxc as the transpiler. This brings significant performance improvements to the development and build workflow. Several code adjustments were needed:
- Namespace exports (
export letin namespaces) converted to class-based patterns - Test module specifiers updated for compatibility
enumnamespaces converted toconstobjects
Tinyglobby
Replaced the glob package with tinyglobby for file pattern matching, which is faster and has a smaller dependency footprint.
isolatedDeclarations Enabled
The isolatedDeclarations compiler option is now enabled for domwise and sleekgrid packages, improving declaration file generation performance.
Package Updates
Updated NPM packages:
typescriptto 7.0.2@serenity-is/tsbuildto 10.3.2viteto 8.1.5@tiptappackages to 3.28.0preactto 10.29.7@preact/signals-coreto 1.14.3@preact/signalsto 2.10.0vitestto 4.1.10esbuildto 0.28.1jsdomto 29.1.1dompurifyto 3.14.2es-module-lexerto 2.3.1@floating-ui/domto 1.8.0tinyglobbyto 0.12.7sortablejs(no major update)globreplaced withtinyglobby
Updated NuGet packages:
- System/ASP.NET Core packages to 10.0.10
Microsoft.Data.SqlClientto 7.0.2Microsoft.NET.Test.Sdkto 18.8.1Spectre.Consoleto 0.57.2NUglifyto 1.22.0System.IO.Abstractions.TestingHelpersto 22.2.0MailKitto 4.17.0Oracle.ManagedDataAccess.Coreto 23.26.200Npgsqlto 10.0.3Dapperto 2.1.79MySqlConnectorto 2.6.1Scribanto 7.2.5OpenIddictto 7.6.0Markdigto 1.3.2SixLabors.ImageSharpto 2.1.13coverlet.collectorto 10.0.0Microsoft.Playwright.Xunit.v3to 1.59.0PuppeteerSharpto 25.3.4
StartSharp-Specific Features
Blog Application Sample
A new Blog application sample has been added under Application Samples, duplicating the functionality found at https://serenity.is/blog. This sample demonstrates:
- File-based Markdown blog post rendering
- A custom
IBlogPostSourceinterface with auto-registration - File watching for hot-reload of blog content
- Friendly URLs with relative link fixing
- Integration with the auto-registration system via
AddAutoRegisteredServices
// IBlogPostSource interface (auto-registered):
[RegisterScoped]
public class BlogPostFileSource : IBlogPostSource
{
public async Task<IEnumerable<BlogPost>> GetPosts()
{
// Reads markdown files from the blog directory
}
}
AddExceptionLogger Extension
The AddExceptionLogger extension now allows configuring an editSettings function, giving more control over how exception log entries are edited in the UI.
CSP Integration
All javascript: URLs have been removed (see CSP Compatibility section above), ensuring compatibility with strict Content Security Policy configurations.
Upgrading to 10.3.5
- Update NuGet packages to 10.3.5
- Update npm packages:
@serenity-is/tsbuildto 10.3.2+
- EntityDialog promise return: If you override
save(),loadById(),delete(),undelete(), orload()in your dialog classes, update the return types to return aPromiseLike<T>:protected override save(callback?, initiator?): PromiseLike<SaveResponse> | false { return super.save(callback, initiator); } - Rename
useGridEditorSave: If you useGridEditController, renameuseGridEditorSavetouseGridEditorServices. - Check
GridEditControllerpermissions: If you use permission-based editing, review the newhasInsertPermission,hasUpdatePermission,canEditItem, andhasDeletePermissionoptions. - Auto-registration: If you want to use the new
[RegisterScoped],[RegisterSingleton],[RegisterTransient]attributes, addservices.AddAutoRegisteredServices()as the last line in yourConfigureServicesmethod. - CSP compatibility: If you have custom links with
javascript:URLs, replace them withrole="button"or event-based handlers. - TypeScript 7: Ensure your
tsconfig.jsonhas the required settings (same as TS6):{ "compilerOptions": { "strict": false, "noUncheckedSideEffectImports": false, "noImplicitOverride": true, "rootDir": "." } } - Review dialogType usage: If you pass dialog types as strings to combo/lookup editors, consider using direct type references or promises for better type safety.