Files
EnvelopeGenerator/COPILOT_CONTEXT_EN.md
TekH 26da78fa22 Refactor ReceiverUI: Rename files, update routes
Renamed Razor component files in ReceiverUI to follow a consistent "Page" naming convention. Updated routes to reference the renamed files. Introduced the root route (`/`) with `Index.razor` as the application entry point.

Updated documentation to reflect the file renames, route changes, and multi-envelope support. Clarified Redis/SQL caching details and deprecated the "Web" frontend in favor of `ReceiverUI`.

These changes improve maintainability, consistency, and developer experience.
2026-06-11 11:38:32 +02:00

14 KiB
Raw Blame History

EnvelopeGenerator — AI Context Reference

Purpose

Digital document signing system with unified Blazor WASM frontend for both Senders and Receivers. Senders create envelopes and place signature fields. Receivers view PDFs, sign documents, export stamped PDFs.

Primary Libraries: DevExpress + PDF.js (PSPDFKit removed)


Deployment Architecture

Two Presentation Projects (Both Required):

  1. EnvelopeGenerator.API (ASP.NET Core Web API)

    • Runs independently (development & production)
    • YARP Reverse Proxy configured via yarp.json
    • Proxies requests to:
      • EnvelopeGenerator.ReceiverUI (Blazor WASM)
      • External Auth.API service
    • Serves as single entry point for all requests
  2. EnvelopeGenerator.ReceiverUI (Blazor WebAssembly)

    • Runs on separate host/port
    • Accessed only through API proxy (not directly)
    • Serves static files (HTML, JS, CSS, WASM)

Request Flow:

Client ? API:8088 (YARP Proxy) ? ReceiverUI:52936 (Blazor WASM)
                                ? Auth.API:9090 (External Auth Service)

Configuration: EnvelopeGenerator.API/yarp.json


ReceiverUI Route Structure

Root Route

Route File Purpose
/ Index.razor Application entry point (landing page).

Sender Routes

Route File Purpose
/sender/login LoginSenderPage.razor Username/password authentication
/sender EnvelopeSenderPage.razor Sender dashboard (envelope list)

Receiver Routes

Route File Purpose
/envelope/login/{EnvelopeKey} LoginReceiverPage.razor Access code authentication for specific envelope
/envelope/{EnvelopeKey} EnvelopeReceiverPage.razor View & sign envelope (PDF.js viewer)

Multi-Envelope Support: Receivers can login to multiple envelopes simultaneously (per-envelope cookie authentication).


Architecture Evolution

Old Architecture (Deprecated)

  • Sender UI: EnvelopeGenerator.Web (Razor Pages + PSPDFKit)
  • Receiver UI: EnvelopeGenerator.ReceiverUI (Blazor WASM + PDF.js)
  • Backend: EnvelopeGenerator.API

Current Architecture

  • Unified Frontend: EnvelopeGenerator.ReceiverUI (Blazor WASM) — Both Senders & Receivers
  • Backend: EnvelopeGenerator.APIBoth Senders & Receivers
  • Libraries: DevExpress + PDF.js
  • PSPDFKit: REMOVED

Solution Structure

Project Target Purpose
EnvelopeGenerator.API net8.0 ASP.NET Core Web API. Backend for both Senders & Receivers. Auth, PDF serving, signature endpoints.
EnvelopeGenerator.ReceiverUI net8.0 WASM Unified Blazor WebAssembly Frontend. UI for both Senders & Receivers. YARP proxy to API.
EnvelopeGenerator.Web net7/8/9 DEPRECATED. Legacy Razor Pages (Sender UI). No longer used.
EnvelopeGenerator.Application multi MediatR CQRS handlers. Business logic.
EnvelopeGenerator.Domain multi Domain models, constants, interfaces.
EnvelopeGenerator.Infrastructure multi EF Core repos, DB context.
EnvelopeGenerator.PdfEditor multi iText7 utilities (NOT used in ReceiverUI).
EnvelopeGenerator.DependencyInjection multi DI registration helpers.
VB.NET projects (Service/Form/BBTests) net462 Legacy. Do NOT touch.

Key Files & Routes

File Route/Purpose
ReceiverUI/Pages/Index.razor / — Application entry point (landing page).
ReceiverUI/Pages/EnvelopeSenderPage.razor /sender — Sender dashboard (envelope list).
ReceiverUI/Pages/EnvelopeReceiverPage.razor /envelope/{key} — Receiver PDF viewer & signing.
ReceiverUI/Pages/LoginSenderPage.razor /sender/login — Sender username/password auth.
ReceiverUI/Pages/LoginReceiverPage.razor /envelope/login/{EnvelopeKey} — Receiver access code auth.
ReceiverUI/wwwroot/js/pdf-viewer.js PDF.js wrapper (zoom, pagination, thumbnails).
ReceiverUI/wwwroot/js/receiver-signature.js Signature pad (draw/type/image).
ReceiverUI/wwwroot/css/envelope-viewer.css EnvelopeViewer styles.
ReceiverUI/Services/AuthService.cs Receiver + Sender authentication.
ReceiverUI/Services/SignatureCacheService.cs Signature caching (Redis/SQL).
API/Controllers/CacheController.cs Signature cache endpoints.

Coordinate System — CRITICAL

Database Format: INCHES (GdPicture14 native)
Origin: Top-left corner
Axes: X right, Y down

Conversion Formulas

From INCHES to Formula Example
DevExpress DX x_DX = x_inches * 100 1.5" ? 150 DX
PDF Points x_pt = x_inches * 72 1.5" ? 108 pt
PDF.js Pixels Normalize ? scale (x_inches / pageWidth) * canvasWidth * scale

A4 Dimensions:

  • Width: 8.27" = 595pt = 827 DX
  • Height: 11.69" = 842pt = 1169 DX

Unit Systems

System Unit Origin Y-Axis
Database (GdPicture14) Inches Top-left Down
PDF.js Pixels Top-left Down
iText7 PDF Points (1/72") Bottom-left Up (flip required)
PSPDFKit Points Top-left REMOVED

EnvelopeReceiver — PDF.js Viewer & Signing

Route: /envelope/{EnvelopeKey}
Tech: PDF.js 3.11.174 + Blazor WASM + configurable quality
File: ReceiverUI/Pages/EnvelopeReceiverPage.razor

Key Features

  1. HiDPI/Retina support (4x quality)
  2. Configurable quality (appsettings.json)
  3. Unlimited zoom (50%-300%)
  4. Ctrl+Wheel global zoom
  5. Resizable thumbnail sidebar (150-400px, localStorage)
  6. Responsive (desktop/mobile)

Configuration

File: ReceiverUI/wwwroot/appsettings.json

{
  "PdfViewer": {
    "ThumbnailBaseScale": 0.75,
    "ThumbnailEnableHiDPI": true,
    "MainCanvasEnableHiDPI": true,
    "ZoomStepPercentage": 5
  }
}

JavaScript API

File: ReceiverUI/wwwroot/js/pdf-viewer.js

window.pdfViewer = {
    initialize(canvasId, pdfDataUrl, dotNetRef),
    renderPage(num),
    renderSignatureButtons(signatures, pageNum, dotNetRef),
    applySignature(signatureId, dataUrl, fullName, position, place),
    zoomIn(), zoomOut(), dispose()
}

Signature Workflow — EnvelopeReceiver

IMPORTANT: iText7 NOT used (GPL license issue). Client-side overlay system only.

Workflow Steps

  1. Page Load:

    • Check SignatureCacheService for cached signature
    • If cached ? skip popup, load signature
    • If not ? show automatic popup (mandatory)
  2. Signature Popup (DxPopup):

    • Cannot close (no X, no ESC, no outside-click)
    • 3 Tabs: Draw (canvas) / Text (font select) / Image (upload)
    • Required: Full name, Place
    • Optional: Position
    • Save ? Store in _capturedSignature, cache via API
  3. Signature Buttons:

    • Render purple "Unterschreiben" buttons at signature field positions
    • Coordinates: INCHES ? POINTS ? Pixels (scaled)
    • File: pdf-viewer.js ? renderSignatureButtons()
  4. Apply Signature (Click "Unterschreiben"):

    • JS: Remove button, create HTML overlay
    • Format: Image + separator + text (Name, Position, Place, Date)
    • NOT stamped on PDF bytes (visual overlay only)
  5. Re-rendering:

    • Zoom/Page change ? recalculate button positions
    • Session state: _capturedSignature (lost on refresh)

Data Model

File: ReceiverUI/Models/SignatureCaptureDto.cs

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

Signature Caching

Purpose: Persist signature across page refreshes (distributed cache: Redis/SQL)

API Endpoints

Controller: API/Controllers/CacheController.cs

  • POST /api/Cache/SignatureCapture/{envelopeKey} — Save
  • GET /api/Cache/SignatureCapture/{envelopeKey} — Load
  • DELETE /api/Cache/SignatureCapture/{envelopeKey} — Delete

Cache Key Format:

signature:91751687-8ae6-4777-bf5f-b8846085e62e:{envelopeKey}

Configuration: appsettings.json

{
  "Cache": {
    "SignatureCacheExpiration": null  // or "02:00:00" for 2h
  }
}

Service

File: ReceiverUI/Services/SignatureCacheService.cs

public class SignatureCacheService {
    Task SaveSignatureAsync(string envelopeKey, SignatureCaptureDto signature);
    Task<SignatureCaptureDto?> GetSignatureAsync(string envelopeKey);
    Task DeleteSignatureAsync(string envelopeKey);
}

Error Handling: Fire-and-forget saves, graceful degradation on load failure.


Sender Login

Route: /sender/login
File: ReceiverUI/Pages/LoginSenderPage.razor
Tech: Bootstrap 5 + DevExpress Blazing Berry theme

AuthService Extension

File: ReceiverUI/Services/AuthService.cs

public enum SenderLoginResult { Success, InvalidCredentials, Error }

public async Task<SenderLoginResult> LoginSenderAsync(string username, string password) {
    var response = await http.PostAsJsonAsync(
        $"{_api.BaseUrl}/api/auth?cookie=true",
        new { username, password });
    
    return response.StatusCode switch {
        HttpStatusCode.OK => SenderLoginResult.Success,
        HttpStatusCode.Unauthorized => SenderLoginResult.InvalidCredentials,
        _ => SenderLoginResult.Error
    };
}

API Integration

Endpoint: POST /api/auth?cookie=true

Request:

{ "username": "TekH", "password": "***" }

Response:

  • 200 OK ? Cookie set, redirect to /sender
  • 401 Unauthorized ? Show error: "Ungültige Anmeldedaten"
  • Other ? Show error: "Serverfehler"

Cookie: HTTP-only, Secure (HTTPS), SameSite=Strict

UI Flow

  1. User enters username + password
  2. Click "Anmelden" or press Enter
  3. Call AuthService.LoginSenderAsync()
  4. Success ? Navigation.NavigateTo("/sender", forceLoad: true)
  5. Error ? Display alert

Receiver Login

Route: /envelope/login/{EnvelopeKey}
File: ReceiverUI/Pages/LoginReceiverPage.razor

Multi-Envelope Support: Cookies are stored per-envelope (e.g., AuthTokenSignFLOWReceiver.{envelopeKey}), allowing simultaneous authentication for multiple envelopes in the same browser session.

AuthService Method

public enum EnvelopeLoginResult { Success, InvalidCode, NotFound, Error }

public async Task<EnvelopeLoginResult> LoginEnvelopeReceiverAsync(string key, string accessCode) {
    var form = new MultipartFormDataContent();
    form.Add(new StringContent(accessCode), "AccessCode");
    
    var response = await http.PostAsync(
        $"{_api.BaseUrl}/api/Auth/envelope-receiver/{Uri.EscapeDataString(key)}", form);
    
    return response.StatusCode switch {
        HttpStatusCode.OK => EnvelopeLoginResult.Success,
        HttpStatusCode.Unauthorized => EnvelopeLoginResult.InvalidCode,
        HttpStatusCode.NotFound => EnvelopeLoginResult.NotFound,
        _ => EnvelopeLoginResult.Error
    };
}

Success: Redirect to /envelope/{key}


NuGet Packages (ReceiverUI)

Package Version Purpose
DevExpress.Blazor.* 25.2.3 UI components (grids, popups, etc.)
SkiaSharp.* 3.119.1 WASM rendering
itext 8.0.5 NOT USED (GPL license)

External CDN:

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

Mistakes History — Do NOT Repeat

Mistake Why Wrong
Using iText7 in EnvelopeReceiver GPL license issue. Use overlay system instead.
Using PSPDFKit Removed from architecture. Use PDF.js + DevExpress.
Hardcoded quality values in PDF.js Use appsettings.json for configurability.
Complex toolbar layouts User wants simplicity. Keep horizontal layout.
Over-designed UI (gradients/badges) User prefers simple text labels.
Ignoring "revert" instructions Revert HTML structure, not just CSS.
BottomMarginBand for signatures Repeats on every page. Use DetailBand.
imageY = (page-1) * 1169 + ann.Y Inflates DetailBand. Calculate per-page.

Development Notes

Deprecated Projects

DO NOT USE:

  • EnvelopeGenerator.Web (Razor Pages) — Replaced by unified ReceiverUI
  • PSPDFKit — Removed, use PDF.js + DevExpress instead

Legacy Projects (VB.NET)

DO NOT TOUCH: EnvelopeGenerator.Service, EnvelopeGenerator.Form, EnvelopeGenerator.BBTests

Signature Coordinate Evidence

File: EnvelopeGenerator.Form/frmFieldEditor.vb (VB.NET)

Private Const SIGNATURE_WIDTH As Single = 1.77   ' inches
Private Const SIGNATURE_HEIGHT As Single = 1.96  ' inches

Sub LoadAnnotation(pElement As Signature, ...)
    oAnnotation.Left = CSng(pElement.X)    ' Direct INCHES assignment
    oAnnotation.Top = CSng(pElement.Y)
End Sub

Proves database uses INCHES natively.


Quick Reference

When working with coordinates:

  1. Database ? UI: INCHES × 72 = PDF Points
  2. UI ? Display: Points × scale = Pixels
  3. iText7 stamping: Flip Y-axis (top-down ? bottom-up)

When adding features:

  1. Check Mistakes History first
  2. Prefer simplicity over complexity
  3. Use appsettings.json for configuration
  4. Keep consistent with existing design (Bootstrap 5 + Blazing Berry)
  5. Unified frontend: ReceiverUI serves both Senders and Receivers

When debugging:

  1. Coordinates: Always check unit system (inches/points/pixels)
  2. Authentication: Check cookie name/domain/SameSite
  3. Cache: Check Redis/SQL connection + key format
  4. Frontend confusion: Only use ReceiverUI (Web is deprecated)

Last Updated: Session 19 (Razor file naming convention + Index route proxy)