Files
EnvelopeGenerator/COPILOT_CONTEXT.md
TekH 99fbb33f1c Migrate PDF.js to DevExpress DxPdfViewer
Transitioned PDF rendering in `EnvelopeReceiverPage.razor`
from `PDF.js` to `DevExpress DxPdfViewer`. Updated code
and documentation to reflect the verified API of
`DevExpress.Blazor.PdfViewer` v25.2.3, addressing its
limitations (e.g., lack of `GoToPageAsync`, `PageNumberChanged`).

Implemented `CustomizeToolbar` for navigation/zoom controls
and manual state tracking for `_currentPage` and `_viewerZoomLevel`.
Replaced JavaScript interop for page count with the `PageCount`
property. Retained the custom thumbnail sidebar due to API
constraints.

Added temporary debug tools for DOM analysis and navigation
testing. Updated `TESTING_CHECKLIST.md` and added
`DEVEXPRESS_V25_LIMITATIONS.md` to document the new strategy,
API limitations, and testing scenarios. Cross-page signature
navigation implemented with state updates, though visible
page changes remain manual.

Prepared for future improvements while ensuring functional
migration to `DxPdfViewer`.
2026-06-30 16:12:05 +02:00

15 KiB

EnvelopeGenerator — Current Workspace Context

Purpose

Digital document signing system for senders and receivers.

  • Senders authenticate, view envelope lists, and manage envelope workflows.
  • Receivers authenticate per envelope, open PDFs, create signatures, and apply them in the viewer.
  • The active UI stack is Blazor Auto with server-side and WebAssembly render modes.
  • Primary UI/PDF libraries are DevExpress and PDF.js.

Active Application Structure

Main Host

Primary active application: EnvelopeGenerator.Server

EnvelopeGenerator.Server is the current runtime host and contains:

  • Blazor server host
  • WebAssembly host integration
  • API controllers
  • authentication/authorization setup
  • Swagger/Scalar setup
  • YARP reverse proxy configuration
  • DevExpress server-side services
  • SQL Server distributed cache setup

Client Project

Client UI project: EnvelopeGenerator.Server.Client

This project contains:

  • WebAssembly-rendered pages
  • client-side services
  • client models and options
  • sender and receiver login flows

Other Projects

  • EnvelopeGenerator.Application — MediatR/CQRS handlers and business logic
  • EnvelopeGenerator.Domain — domain models, constants, shared abstractions
  • EnvelopeGenerator.Infrastructure — EF Core and infrastructure services
  • EnvelopeGenerator.PdfEditor — PDF-related backend utilities
  • EnvelopeGenerator.API — still exists in the solution, but the current merged app host is EnvelopeGenerator.Server

Legacy / Do Not Touch

  • EnvelopeGenerator.Service
  • EnvelopeGenerator.Form
  • EnvelopeGenerator.BBTests
  • EnvelopeGenerator.CommonServices

Current Hosting Model

EnvelopeGenerator.Server/Program.cs currently configures:

  • AddRazorComponents() with both interactive server and interactive WebAssembly components
  • AddControllers() and MapControllers()
  • JWT authentication for sender and receiver flows
  • cookie authentication
  • authorization policies using AuthScheme.Sender, AuthScheme.Receiver, AuthPolicy.Sender, AuthPolicy.Receiver
  • AddReverseProxy() with yarp.json
  • Swagger / OpenAPI / Scalar
  • distributed SQL Server cache
  • DevExpress Blazor and DevExpress PDF Viewer server-side services
  • request localization middleware

This means the active app is a merged UI + API host.


Reverse Proxy

Config file: EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json

Current YARP usage is focused on AuthHub forwarding, not a general /api/* -> EnvelopeGenerator.API proxy.

Configured routes forward:

  • POST /api/auth -> AuthHub /api/auth/sign-flow
  • POST /api/Auth/envelope-receiver/{key} -> AuthHub /api/auth/envelope-receiver/{key}?cookie=true

Active Routes and Files

WebAssembly Pages (EnvelopeGenerator.Server.Client)

Route File Render Mode Purpose
/ EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/IndexPage.razor WebAssembly Landing page
/sender/login EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginSenderPage.razor WebAssembly Sender login
/sender EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor WebAssembly (prerender: false) Sender dashboard
/envelope/login/{EnvelopeKey} EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginReceiverPage.razor WebAssembly Receiver login

Server Pages (EnvelopeGenerator.Server)

Route File Render Mode Purpose
/envelope/{EnvelopeKey} EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor InteractiveServer Main receiver PDF viewer and signing page
/envelope/DxPdfViewer EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor InteractiveServer DevExpress PDF Viewer test page
/envelope/{EnvelopeKey}/DxReportViewer EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxReportViewer.razor InteractiveServer DevExpress report-based PDF rendering
/envelope/Embed EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_embed.razor InteractiveServer Embedded browser PDF view test page

Current API Location

The active application exposes controllers from: EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers

Current controller set includes:

  • AnnotationController
  • AuthController
  • CacheController
  • ConfigController
  • DocumentController
  • EmailTemplateController
  • EnvelopeController
  • EnvelopeReceiverController
  • EnvelopeTypeController
  • HistoryController
  • LocalizationController
  • ReadOnlyController
  • ReceiverController
  • SignatureController
  • TfaRegistrationController

Do not assume API behavior lives only in EnvelopeGenerator.API; the active merged host contains controller endpoints directly.


Authentication Model

Sender

Client login page uses EnvelopeGenerator.Server.Client/Services/AuthService.cs.

Key sender endpoints:

  • POST /api/auth?cookie=true — login
  • GET /api/auth/check — current sender access check
  • POST /api/auth/logout — logout

Receiver

Receiver authentication is per envelope.

Key receiver endpoints used by client services:

  • POST /api/Auth/envelope-receiver/{envelopeKey} — submit access code
  • GET /api/auth/check/envelope/{envelopeKey} — check access
  • POST /api/auth/logout/envelope/{envelopeKey} — logout receiver for one envelope

Receiver cookie resolution in server auth uses an envelope-specific cookie name derived from:

  • AuthTokenSignFLOWReceiver.{envelopeKey} pattern

Receiver Server-Side Authorization

EnvelopeReceiverPage.razor does not rely on its own API access-check call for page authorization.

It uses:

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs

Behavior:

  • tries the current HttpContext.User
  • if needed, reads the per-envelope receiver cookie directly
  • validates the JWT with the receiver auth scheme
  • verifies the token subject matches the route envelope key

Receiver Page Data Loading

Main server-side page data service:

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs

This service loads directly via MediatR and distributed cache:

  • document bytes
  • receiver envelope data
  • signature placeholders
  • cached signature data

For signature placeholders, the service:

  • reads document receiver elements
  • filters them for the authenticated receiver
  • converts coordinates to UnitOfLength.Point before UI use

Receiver PDF Viewer

Main file: EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor

Current receiver viewer characteristics:

  • route: /envelope/{EnvelopeKey}
  • render mode: InteractiveServer
  • PDF rendering: Migration in progress from PDF.js to DxPdfViewer
  • toolbar: page navigation, zoom, thumbnail toggle, signature navigation, signature reset
  • signature popup: DxPopup
  • thumbnail sidebar: resizable and stored in localStorage

⚠️ CRITICAL: DevExpress DxPdfViewer Control Requirements

Verified API for installed DevExpress.Blazor.PdfViewer v25.2.3:

Property Access Notes
DocumentContent [Parameter] GET/SET Feed PDF as byte[]
ZoomLevel [Parameter] GET/SET Factor (not percentage): 1.5 = 150%
IsSinglePagePreview [Parameter] GET/SET Single page mode
CssClass [Parameter] GET/SET CSS class
DocumentName [Parameter] GET/SET Download filename
SizeMode [Parameter] GET/SET Small / Medium / Large
PageCount Read-only GET Total pages — no JS call needed
ActivePageIndex Read-only GET Current page (0-based) — cannot SET
CustomizeToolbar Event Only available toolbar event

Does NOT exist in v25.2.3 — do NOT use:

  • GoToPageAsync()
  • GoToNextPageAsync()
  • ZoomAsync()
  • PageNumberChanged event
  • ZoomLevelChanged event
  • ToolbarVisible property

Correct approach:

<DxPdfViewer @ref="_pdfViewer"
             DocumentContent="@_pdfDocumentContent"
             ZoomLevel="@_viewerZoomLevel"
             IsSinglePagePreview="true"
             CustomizeToolbar="OnCustomizeToolbar" />
// ZoomLevel: always divide by 100 (factor, not percentage)
_viewerZoomLevel = _currentZoom / 100d;  // 150 -> 1.5

// PageCount: read directly, no JS needed
_totalPages = _pdfViewer.PageCount;

// Page navigation: only via CustomizeToolbar buttons
protected void OnCustomizeToolbar(ToolbarModel toolbarModel)
{
    toolbarModel.AllItems.Clear();
    var nextButton = new ToolbarItem
    {
        IconCssClass = "dx-icon-chevronnext",
        Enabled = _currentPage < _totalPages,
        Click = async (args) =>
        {
            _currentPage++;
            _viewerZoomLevel = _currentZoom / 100d;
            await InvokeAsync(StateHasChanged);
            await RenderSignatureButtonsAsync();
        }
    };
    toolbarModel.AllItems.Add(nextButton);
}

JavaScript role after migration:

  • Overlay geometry calculations only
  • Thumbnail rendering via PDF.js helper
  • Custom UI interactions (sidebar resize, signature canvas)
  • NOT for controlling DxPdfViewer page or zoom

See DEVEXPRESS_V25_LIMITATIONS.md for complete verified API reference.

JS Assets

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.js
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js

CSS

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css

PDF.js CDN

  • https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js
  • https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css

Signature Workflow

Receiver signatures are handled as a viewer overlay workflow.

Current behavior

  1. Server-side authorization validates receiver access.
  2. The page loads document bytes, receiver data, signature placeholders, and cached signature state.
  3. If no cached signature exists, the signature popup opens automatically.
  4. Receiver creates signature using one of three tabs:
    • draw
    • text
    • image
  5. Required metadata:
    • full name
    • place
  6. Optional metadata:
    • position
  7. Clicking a signature placeholder applies the signature as a client-side overlay in the PDF viewer.

Important note

Although itext is referenced by the server project, the current receiver page signing flow is not PDF stamping-based. The active receiver UI uses client-side overlay behavior in the viewer.

Signature DTO

EnvelopeGenerator.Server.Client/Models/SignatureCaptureDto.cs

public sealed record SignatureCaptureDto {
    public required string DataUrl { get; init; }
    public required string FullName { get; init; }
    public string Position { get; init; } = "";
    public required string Place { get; init; }
}

Signature Cache

Active cache model

The current receiver page cache flow is handled directly in the server project through:

  • EnvelopeReceiverPageDataService
  • IDistributedCache
  • SQL Server distributed cache configuration from Program.cs

Cache key format

Current server-side key prefix:

  • envelope-generator.receiver-ui.signature:{receiverSignature}

This is different from an envelope-key-only cache convention.

Config

EnvelopeGenerator.Server/EnvelopeGenerator.Server/Options/CacheOptions.cs

  • section name: Cache
  • option: SignatureCacheExpiration

A cache API controller also exists in:

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/CacheController.cs

Sender Dashboard

Main file: EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor

Current behavior:

  • checks sender access through AuthService.CheckSenderAccessAsync()
  • redirects to /sender/login when unauthorized
  • loads envelope list through client EnvelopeService
  • separates envelopes into active/completed tabs
  • uses DevExpress DxGrid

The sender page is active, but create/edit/delete actions are still marked with TODO behavior in the UI page.


Localization

Current server host localization setup in Program.cs:

  • supported cultures: de-DE, en-US
  • request localization middleware is enabled
  • QueryStringRequestCultureProvider is added
  • cookie-based localization services are registered via AddCookieBasedLocalizer()

Do not assume the old ReceiverUI-only localStorage culture approach is the current source of truth for the active host.


Coordinate System

Source data

Database signature coordinates are still based on:

  • unit: inches
  • origin: top-left
  • axes: X right, Y down

Relevant conversions

  • inches -> PDF points: x_pt = x_inches * 72
  • inches -> DevExpress DX units: x_dx = x_inches * 100

Current receiver page behavior

The server page data service converts signature placeholders to points before sending them into the viewer workflow.

Unit systems to keep in mind

System Unit Origin Y-axis
Database Inches Top-left Down
PDF.js display Pixels Top-left Down
PDF points Points Depends on PDF model Depends on consumer
DevExpress DX 1/100 inch style coordinates Top-left-oriented usage in this app Down-oriented usage

Key Services and Files

Client services

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocumentService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureCacheService.cs

Server services

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeAuthService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/IEnvelopeAuthService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs

Server config and host files

  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs
  • EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json

Working Rules for This Workspace

  • Treat EnvelopeGenerator.Server as the active main application host.
  • Treat EnvelopeGenerator.Server.Client as the active client UI project.
  • Prefer current Server / Server.Client paths over old WebUI / ReceiverUI references.
  • Do not use EnvelopeGenerator.Web or EnvelopeGenerator.ReceiverUI as the primary implementation target unless explicitly asked.
  • Do not modify the legacy VB.NET projects unless explicitly requested.
  • For receiver PDF/signature work, prefer the current PDF.js-based flow in EnvelopeReceiverPage.razor.
  • For DevExpress PDF viewer issues, remember server-side services are registered in EnvelopeGenerator.Server.

Last Updated: 2026-06-29