docs: complete project research
This commit is contained in:
@@ -1,320 +1,242 @@
|
||||
# Pitfalls Research
|
||||
|
||||
**Domain:** Real estate broker web app with custom e-signature and document signing (Utah/WFRMLS)
|
||||
**Researched:** 2026-03-19
|
||||
**Confidence:** HIGH
|
||||
**Domain:** Real estate broker web app — v1.1 additions: AI field placement, expanded field types, agent saved signature, filled document preview
|
||||
**Researched:** 2026-03-21
|
||||
**Confidence:** HIGH (all pitfalls grounded in the actual v1.0 codebase reviewed; no speculative claims)
|
||||
|
||||
---
|
||||
|
||||
## Context: What v1.1 Is Adding to the Existing System
|
||||
|
||||
The v1.0 codebase has been reviewed. Key facts that shape every pitfall below:
|
||||
|
||||
- `SignatureFieldData` (schema.ts) has **no `type` field** — it stores only `{ id, page, x, y, width, height }`. Every field is treated as a signature.
|
||||
- `FieldPlacer.tsx` has **one draggable token** labeled "Signature" — no other field types exist in the palette.
|
||||
- `SigningPageClient.tsx` **iterates `signatureFields`** and opens the signature modal for every field. It has no concept of field type.
|
||||
- `embed-signature.ts` **only draws PNG images** — no logic for text, checkboxes, or dates.
|
||||
- `prepare-document.ts` uses `@cantoo/pdf-lib` (confirmed import), fills AcroForm text fields and draws blue rectangles for signature placeholders. It does not handle the new field types.
|
||||
- Prepared PDF paths are stored as relative local filesystem paths (not Vercel Blob URLs). The signing route builds absolute paths from these.
|
||||
- Agent saved signature: no infrastructure exists yet. The v1.0 `SignatureModal` checks `localStorage` for a saved signature — that is the only "save" mechanism today, and it is per-browser only.
|
||||
|
||||
---
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
### Pitfall 1: Custom E-Signature Has No Tamper-Evident PDF Hash
|
||||
### Pitfall 1: Breaking the Signing Page by Adding Field Types Without Type Discrimination
|
||||
|
||||
**What goes wrong:**
|
||||
The signed PDF is stored, but there is no cryptographic hash (SHA-256 or similar) computed at the moment of signing and embedded in or stored alongside the document. If the PDF is ever challenged in court, there is no way to prove the document was not modified after signing. The signature image becomes legally just "a drawing on a page."
|
||||
`SignatureFieldData` has no `type` field. `SigningPageClient.tsx` opens the signature-draw modal for every field in `signatureFields`. When new field types (text, checkbox, initials, date, agent-signature) are stored in that same array with only coordinates, the client signing page either (a) shows a signature canvas for a checkbox field, or (b) crashes with a runtime error when it encounters a field type it doesn't handle, blocking the entire signing page.
|
||||
|
||||
**Why it happens:**
|
||||
Developers focus on capturing the signature canvas image and embedding it into the PDF. The hash/integrity step feels like an edge case until a dispute occurs years later. Utah courts and ESIGN/UETA require the system to prove document integrity, not just that a signature image exists.
|
||||
The schema change is made on the agent side first (adding a `type` discriminant to `SignatureFieldData` and new field types to `FieldPlacer`), but the signing page is not updated in the same commit. Even one deployed document with mixed field types — sent before the signing page update — will be broken for that client.
|
||||
|
||||
**How to avoid:**
|
||||
After embedding the signature image into the PDF and before storing the final file: compute a SHA-256 hash of the complete signed PDF bytes. Store this hash in the database record alongside the document. Optionally, embed the hash and a certificate-of-completion JSON blob as a PDF metadata/attachment. On any challenge, recompute the hash against the stored file and compare.
|
||||
Add `type` to `SignatureFieldData` as a string literal union **before** any field placement UI changes ship. Make the signing page's field renderer branch on `type` defensively: unknown types default to a placeholder ("not required") rather than throwing. Ship both changes atomically — schema migration, `FieldPlacer` update, and `SigningPageClient` update must be deployed together. Never have a deployed state where the schema supports types the signing page doesn't handle.
|
||||
|
||||
**Warning signs:**
|
||||
- The signing record only stores the signature image URL and a timestamp, with no document fingerprint.
|
||||
- No "certificate of completion" is generated alongside the signed PDF.
|
||||
- The audit log references document events but not the final document hash.
|
||||
- `SignatureFieldData` in `schema.ts` gains a `type` property but `SigningPageClient.tsx` still iterates fields without branching on it.
|
||||
- The FieldPlacer palette has more tokens than the signing page has rendering branches.
|
||||
- A document is sent before the signing page is updated to handle the new types.
|
||||
|
||||
**Phase to address:**
|
||||
Document signing backend — before any client is sent a signing link.
|
||||
Phase 1 of v1.1 (schema and signing page update) — must be the first change, before any AI or UI work touches field types.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 2: Audit Trail is Incomplete and Would Fail Court Challenge
|
||||
### Pitfall 2: AI Coordinate System Mismatch — OpenAI Returns CSS-Space Percentages, pdf-lib Expects PDF Points
|
||||
|
||||
**What goes wrong:**
|
||||
The system logs that a document was signed and stores an IP address. But if challenged, the opposing party argues the signer's identity cannot be verified, the viewing timestamp is missing, or the sequence of events (sent → opened → signed) cannot be reconstructed. Courts have rejected e-signatures precisely because the audit trail only showed the signature event, not the full ceremony.
|
||||
The OpenAI response for field placement will return bounding boxes in one of several formats: percentage of page (0–1 or 0–100), pixel coordinates at an assumed render resolution, or CSS-style top-left origin. The existing `SignatureFieldData` schema stores **PDF user space coordinates** (bottom-left origin, points). When the AI output is stored without conversion, every AI-placed field appears at the wrong position — often inverted on the Y axis. The mismatch is not obvious during development if you test with PDFs where fields land approximately near the correct area.
|
||||
|
||||
**Why it happens:**
|
||||
Developers build the "happy path" (sign button clicked → PDF stored). Pre-signing events (email sent, link opened, document viewed) are not logged because they seem unimportant until litigation. Federal district courts have held that detailed e-signature audit logs satisfy authentication requirements; gaps in the log create exploitable weaknesses.
|
||||
The current `FieldPlacer.tsx` already has a correct `screenToPdfCoords` function for converting drag events. But that function takes rendered pixel dimensions as input. When AI output arrives as a JSON payload, developers mistakenly store the raw AI coordinates directly into the database without passing them through the same conversion. The sign-on-screen overlay in `SigningPageClient.tsx` then applies `getFieldOverlayStyle()` which expects PDF-space coords, producing the wrong position.
|
||||
|
||||
**Concrete example from the codebase:**
|
||||
`screenToPdfCoords` in `FieldPlacer.tsx` computes:
|
||||
```
|
||||
pdfY = ((renderedH - screenY) / renderedH) * pageInfo.originalHeight
|
||||
```
|
||||
If the AI returns a y_min as fraction of page height from the top (0 = top), storing it directly as `field.y` means the field appears at the bottom of the page instead of the top, because PDF Y=0 is the bottom.
|
||||
|
||||
**How to avoid:**
|
||||
Log every event in the signing ceremony as a separate, timestamped, server-side record: (1) document prepared, (2) signing email sent with link hash, (3) link opened (IP, user agent, timestamp), (4) document viewed/scrolled, (5) signature canvas drawn/submitted, (6) final PDF hash computed and stored. All events must be stored server-side — never trust client-reported timestamps. Include: signer email, IP address, user-agent string, timezone offset, and event type for every record.
|
||||
Define a canonical AI output format contract before building the prompt. Use normalized coordinates (0–1 fractions from top-left) in the AI JSON response, then convert server-side using a single `aiCoordsToPagePdfSpace(norm_x, norm_y, norm_w, norm_h, pageWidthPts, pageHeightPts)` utility. This utility mirrors the existing `screenToPdfCoords` logic. Unit-test it against a known Utah purchase agreement with known field positions before shipping.
|
||||
|
||||
**Warning signs:**
|
||||
- Audit records are only written on the POST to submit the signature, not on GET of the signing page.
|
||||
- Timestamps come from the client's browser clock.
|
||||
- "Email sent" is logged in the email provider's dashboard but not in the app's own database.
|
||||
- AI-placed fields appear clustered at the bottom or top of the page regardless of document content.
|
||||
- The AI integration test uses visual eyeballing rather than coordinate assertions.
|
||||
- The conversion function is not covered by the existing test suite (`prepare-document.test.ts`).
|
||||
|
||||
**Phase to address:**
|
||||
Email-link signing flow implementation; auditing must be wired in before first signing ceremony, not added later.
|
||||
AI field placement phase — write the coordinate conversion utility and its test before the OpenAI API call is made.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 3: Signing Link is Replayable and Has No One-Time Enforcement
|
||||
### Pitfall 3: OpenAI Token Limits on Large Utah Real Estate PDFs
|
||||
|
||||
**What goes wrong:**
|
||||
A signing link is emailed to the client. The link contains a token (e.g., a UUID or JWT). After the client signs, the link still works — anyone who intercepts, forwards, or finds the link in an inbox can re-open the signing page, potentially submitting a second signature or viewing the partially signed document.
|
||||
Utah standard real estate forms (REPC, listing agreements, buyer representation agreements) are 10–30 pages. Sending the raw PDF bytes or a base64-encoded PDF to GPT-4o-mini will immediately hit the 128k context window limit for multi-page forms, or produce truncated/hallucinated field detection when the document is silently cut off mid-content. GPT-4o-mini's vision context limit is further constrained by image tokens — a single PDF page rendered at 72 DPI costs roughly 1,700 tokens; a 20-page document at standard resolution consumes ~34,000 tokens before any prompt text.
|
||||
|
||||
**Why it happens:**
|
||||
Developers treat the signing token as a read-only session key. Invalidating it requires server-side state, which feels at odds with stateless JWT approaches. The edge case of link forwarding or second-device access feels rare.
|
||||
Developers prototype with short test PDFs (2–3 pages) where the approach works, then discover it fails on production forms. The failure mode is not a hard error — the API returns a response, but field positions are wrong or missing because the model never saw the later pages.
|
||||
|
||||
**How to avoid:**
|
||||
Signing tokens must be stored in the database with a `used_at` timestamp column. On every request to the signing page, check: (1) token exists, (2) token is not expired (recommend 72-hour TTL for real estate — clients may not check email immediately, but 15-minute windows used in authentication magic links are too short for document signing), (3) `used_at` is null. On signature submission success, set `used_at` immediately before returning the success response. Use a database transaction to prevent race conditions. If the link is accessed after use, display "This document has already been signed" with a link to contact the agent — never re-render the signing canvas.
|
||||
Page-by-page processing: render each PDF page to a base64 PNG (using `pdfjs-dist` or `sharp` on the server), send each page image in a separate API call, then merge the field results. Cap input image resolution to 1024px wide (sufficient for field detection). Set a token budget guard before each API call and log when pages approach the limit. Use structured output (JSON mode) so partial responses fail loudly rather than silently returning incomplete data.
|
||||
|
||||
**Warning signs:**
|
||||
- Signing token is a stateless JWT with no server-side revocation mechanism.
|
||||
- Loading the signing URL twice shows the signature canvas both times.
|
||||
- No `signed_at` or `used_at` column exists on the signing link record.
|
||||
- AI analysis is tested with only a 2-page or 3-page sample PDF.
|
||||
- The implementation sends the entire PDF to OpenAI in a single request.
|
||||
- Field detection success rate degrades noticeably on page 8+.
|
||||
|
||||
**Phase to address:**
|
||||
Email-link signing flow — token generation and validation logic must include one-time enforcement from the start.
|
||||
AI integration phase — establish the page-by-page pipeline pattern before testing with real Utah forms.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 4: PDF Coordinate System Mismatch Places Signatures in Wrong Positions
|
||||
### Pitfall 4: Prompt Design — AI Hallucinates Fields That Don't Exist or Misses Required Fields
|
||||
|
||||
**What goes wrong:**
|
||||
Signature fields are placed on the PDF using coordinates from the browser's click/drag UI (origin: top-left, Y increases downward). When the signature image is embedded into the PDF using pdf-lib or a similar library (origin: bottom-left, Y increases upward), the signature appears in the wrong position — often mirrored vertically or offset significantly. This gets worse with rotated PDF pages (common in scanned real estate forms).
|
||||
Without a carefully constrained prompt, GPT-4o-mini will "helpfully" infer field locations that don't exist in the PDF (e.g., detecting a printed date as a fillable date field) or will use inconsistent field type names that don't match the application's `type` enum (`"text_input"` instead of `"text"`, `"check_box"` instead of `"checkbox"`). This produces spurious fields in the agent's document and breaks the downstream field type renderer.
|
||||
|
||||
**Why it happens:**
|
||||
PDF's coordinate system is inherited from PostScript and is the opposite of every web/canvas coordinate system. Developers test with a simple unrotated letter-size PDF and it appears to work, then discover the mismatch when processing actual Utah real estate forms that may be rotated or have non-standard page sizes.
|
||||
The default behavior of vision models is to be helpful and infer structure. Without explicit constraints (exact allowed types, instructions to return empty array when no fields exist, max field count), the output is non-deterministic and schema-incompatible.
|
||||
|
||||
**How to avoid:**
|
||||
Always convert click/placement coordinates from viewer space to PDF space using the library's own conversion APIs. In PDF.js: use `viewport.convertToPdfPoint(x, y)`. In pdf-lib: the page's height must be subtracted from Y coordinates when mapping from top-left screen space. Write a coordinate conversion unit test against a known PDF page size: place a signature at the visual center of the page and assert the embedded result matches `(pageWidth/2, pageHeight/2)` in PDF points. For rotated pages, read and apply the page's `Rotate` key before computing placement — a page rotated 90 degrees requires a full matrix transform, not just a Y-flip.
|
||||
Use OpenAI's structured output (JSON schema mode) with an explicit enum for field types matching the application's type discriminant exactly. Include a negative instruction: "Only detect fields that have an explicit visual placeholder (blank line, box, checkbox square) — do not infer fields from printed text labels." Include a `confidence` score per field so the agent UI can filter low-confidence placements. Validate the response JSON against a Zod schema server-side before storing — reject the entire AI response if any field has an invalid type.
|
||||
|
||||
**Warning signs:**
|
||||
- Signature placement is tested only on a single blank PDF created in the app, not on actual Utah real estate forms.
|
||||
- The placement code contains a raw Y subtraction without checking for page rotation.
|
||||
- Signatures look correct in the browser preview but misaligned in the downloaded PDF.
|
||||
- The prompt asks the model to "detect all form fields" without specifying what counts as a field.
|
||||
- The response is stored directly in the database without Zod validation.
|
||||
- The agent sees unexpected fields on pages with no visual placeholders.
|
||||
|
||||
**Phase to address:**
|
||||
PDF field detection and signature placement — before any user-facing drag-and-drop placement UI is built.
|
||||
AI integration phase — validate prompt output against Zod before the first real Utah form is tested.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 5: Scraping utahrealestate.com Forms Library Violates Terms of Service
|
||||
### Pitfall 5: Agent Saved Signature Stored as Raw DataURL — Database Bloat and Serving Risk
|
||||
|
||||
**What goes wrong:**
|
||||
The app authenticates to utahrealestate.com with Teressa's credentials and scrapes the forms library (PDFs of purchase agreements, listing agreements, etc.) using HTTP requests or a headless browser. This appears to work initially, then breaks when the site updates its session handling, adds bot detection, or changes URL structures. More critically, it violates the platform's Terms of Service and the WFRMLS data licensing agreement, which restricts data use to authorized products only.
|
||||
|
||||
**Why it happens:**
|
||||
utahrealestate.com has partnered with SkySlope Forms for its digital forms solution. There is no documented public API for the forms library. Using Teressa's credentials to download PDFs looks like a clean solution — it's just automating what she does manually. The ToS implication is easy to overlook.
|
||||
A canvas signature exported as `toDataURL('image/png')` produces a base64-encoded PNG string. A typical signature on a 400x150 canvas is 15–60KB as base64. If this is stored directly in the database (e.g., a `TEXT` column in the `users` table), every query that fetches the user row will carry 15–60KB of base64 data it may not need. More critically, if the dataURL is ever sent to the client to pre-populate a form field, it exposes the full signature as a downloadable string in page source.
|
||||
|
||||
**How to avoid:**
|
||||
Do not scrape the utahrealestate.com forms library programmatically. Instead: (1) Identify which forms Teressa uses most frequently (purchase agreement, listing agreement, buyer rep agreement, addendums). (2) Download those PDFs manually once and store them as static assets in the app's own file storage. Utah Division of Real Estate also publishes state-approved forms publicly at commerce.utah.gov — these are explicitly public domain and safe to embed. (3) For any forms that must come from the MLS, treat the upload step as a manual agent action: Teressa uploads the PDF herself from her downloads, then the app processes it. This also means the app is not fragile to site changes.
|
||||
Store the signature as a file (Vercel Blob or the existing `uploads/` directory), and store only the file path/URL in the database. On the signing page and preview, serve the signature through an authenticated API route that streams the file bytes — never expose the raw dataURL to the client page. Alternatively, convert the dataURL to a `Uint8Array` immediately on the server (for PDF embedding only) and discard the string — only the file path goes to the DB.
|
||||
|
||||
**Warning signs:**
|
||||
- The app stores Teressa's utahrealestate.com password in its own database or environment variables to automate login.
|
||||
- The forms-fetching code contains hardcoded URLs pointing to utahrealestate.com paths.
|
||||
- A site redesign or session expiry would break the core import flow.
|
||||
- A `savedSignatureDataUrl TEXT` column is added to the `users` table.
|
||||
- The agent dashboard page fetches the user row and passes `savedSignatureDataUrl` to a React component prop.
|
||||
- The signature appears in the React devtools component tree as a base64 string.
|
||||
|
||||
**Phase to address:**
|
||||
Forms import feature — the architecture decision must be made before any scraping code is written.
|
||||
Agent saved signature phase — establish the storage pattern (file + path, not dataURL + column) before any signature saving UI is built.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 6: IDX Listings Displayed Without Required Broker Attribution and Disclaimers
|
||||
### Pitfall 6: Race Condition — Agent Updates Saved Signature While Client Is Mid-Signing
|
||||
|
||||
**What goes wrong:**
|
||||
Listings from WFRMLS are displayed on the public site without the required listing broker attribution (the co-listing brokerage name, not just Teressa's), without the MLS-required disclaimer text, or with data that has been modified (e.g., description truncated, price formatted differently). NAR IDX policy violations can result in fines up to $15,000 or loss of MLS access — which would effectively end Teressa's ability to operate.
|
||||
The agent draws a new saved signature and saves it while a client has the signing page open. The signing page has already loaded the signing request data (including `signatureFields`). When the agent applies their new saved signature to an agent-signature field and re-prepares the document, there are now two versions of the prepared PDF on disk: the one the client is looking at and the newly generated one. If the client submits their signature concurrently with the agent's re-preparation, `embedSignatureInPdf()` may read a partially-written prepared PDF (before the atomic rename completes) or the document may be marked "Sent" again after already being in "Viewed" state, breaking the audit trail.
|
||||
|
||||
**Why it happens:**
|
||||
IDX compliance rules feel like fine print. Developers display the data that looks good and skip the attribution fields because they seem redundant on a solo agent site. The disclaimer is added as a one-time footer and then forgotten when listing detail pages are added later.
|
||||
The existing prepare flow in `PreparePanel.tsx` allows re-preparation of Draft documents. Once agent signing is added, the agent can re-run preparation on a "Sent" or "Viewed" document to swap their signature, creating a mutable prepared PDF while a client session is active.
|
||||
|
||||
**How to avoid:**
|
||||
Every listing display page (card and detail) must render: (1) listing broker/office name from the `ListOfficeName` field, not Teressa's brokerage; (2) the WFRMLS-required disclaimer text verbatim on every page where IDX data appears; (3) a "Last Updated" timestamp pulled from the feed, not the app's cache write time; (4) no modified listing descriptions or prices. Implement a compliance checklist as a code review gate before any listing-display page ships. Confirm the exact required disclaimer text with WFRMLS directly — it changes with NAR policy updates (2024 settlement, 2025 seller options policy).
|
||||
Lock prepared documents once the first signing link is sent. Gate the agent re-prepare action behind a confirmation: "Resending will invalidate the existing signing link — the client will receive a new email." On confirmation, atomically: (1) mark the old signing token as `usedAt = now()` with reason "superseded", (2) delete the old prepared PDF (or rename to `_prepared_v1.pdf`), (3) generate a new prepared PDF, (4) issue a new signing token, (5) send a new email. This prevents mid-session clobber. The existing `embedSignatureInPdf` already uses atomic rename (`tmp → final`) which prevents partial-read corruption — preserve this.
|
||||
|
||||
**Warning signs:**
|
||||
- Listing detail pages render only the property data, with no co-brokerage attribution field.
|
||||
- The IDX disclaimer appears only on the search results page, not on individual listing detail pages.
|
||||
- The app omits the buyer's agent compensation disclosure field added as a NAR 2024 settlement requirement.
|
||||
- Agent can click "Prepare and Send" on a document with status "Sent" without any confirmation dialog.
|
||||
- The prepared PDF path is deterministic and overwritten in place (e.g. always `{docId}_prepared.pdf`).
|
||||
- No "superseded" state exists in the `signingTokens` table.
|
||||
|
||||
**Phase to address:**
|
||||
Public listings feature — compliance requirements must be treated as acceptance criteria, not post-launch polish.
|
||||
Agent signing phase — implement the supersede-and-resend flow before any agent signature is applied to a sent document.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 7: Stale Listings Show Off-Market Properties as Active
|
||||
### Pitfall 7: Filled Preview Is Served From the Same Path as the Prepared PDF — Stale Preview After Field Changes
|
||||
|
||||
**What goes wrong:**
|
||||
The app caches WFRMLS listing data in a database and refreshes it on a slow schedule (e.g., once per day via a cron job, or only when a user loads the page). A property that went under contract or sold yesterday still shows as active on the site. Clients contact Teressa about a property that is no longer available, damaging her professional credibility. WFRMLS policy expects off-market listings to be removed within 24 hours.
|
||||
The agent makes changes to field placement or pre-fill values after generating a preview. The preview file on disk is now stale. The preview URL is cached by the browser (or a CDN). The agent sees the old preview and believes the document is correct, then sends it to the client. The client receives a document with the old pre-fill values, not the updated ones.
|
||||
|
||||
**Why it happens:**
|
||||
Frequent API polling adds infrastructure complexity. A daily batch job feels sufficient. The RESO OData API returns a `ModificationTimestamp` field that allows delta syncing, but developers often do full re-fetches instead, which is slow and rate-limited.
|
||||
The existing `prepare-document.ts` writes to a deterministic path: `{docId}_prepared.pdf`. If the preview is served from the same path, any browser cache of that URL shows the old version. The agent has no visual indication that the preview is stale.
|
||||
|
||||
**How to avoid:**
|
||||
Implement delta sync: query the WFRMLS RESO API with a `$filter` on `ModificationTimestamp gt [last_sync_time]` to fetch only changed listings since the last run. Schedule this to run hourly via a reliable background job (not WP-Cron or in-process timers — use a proper scheduler like Vercel Cron or a dedicated worker). When a listing's `StandardStatus` transitions to Closed, Withdrawn, or Expired, remove it from the public display immediately, not at next full refresh. Display a "Last Updated" timestamp on each listing page so both users and MLS auditors can verify freshness.
|
||||
Generate preview PDFs to a separate path with a timestamp or version suffix: `{docId}_preview_{timestamp}.pdf`. Never serve the preview from the same path as the final prepared PDF. Add a "Preview is stale — regenerate before sending" banner that appears when `signatureFields` or `textFillData` are changed after the last preview was generated. Store `lastPreviewGeneratedAt` in the document record and compare to `updatedAt`. The "Send" button should be disabled until a fresh preview has been generated (or explicitly skipped by the agent).
|
||||
|
||||
**Warning signs:**
|
||||
- The sync job runs once per day.
|
||||
- The sync is triggered by a user page load rather than a scheduled job.
|
||||
- There is no `ModificationTimestamp` filter in the API query — all listings are re-fetched every run.
|
||||
- The app has no process for immediately hiding a listing when its status changes to off-market.
|
||||
- The preview endpoint serves `/api/documents/{id}/prepared` without a cache-busting mechanism.
|
||||
- The agent can modify fields after generating a preview and the preview URL does not change.
|
||||
- No "stale preview" indicator exists in the UI.
|
||||
|
||||
**Phase to address:**
|
||||
Listings sync infrastructure — before the public site goes live with real listing data.
|
||||
Filled document preview phase — establish the versioned preview path and staleness indicator before the first preview is rendered.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 8: PDF Form Field Detection Relies on Heuristics That Fail on Non-Standard Forms
|
||||
### Pitfall 8: Memory Issues Rendering Large PDFs for Preview on the Server
|
||||
|
||||
**What goes wrong:**
|
||||
The app attempts to auto-detect signature fields in uploaded Utah real estate PDFs using text-layer heuristics (searching for the word "Signature" or "_____" underscores). This works on forms with searchable text layers, fails silently on scanned PDFs (no text layer), and places fields incorrectly on multi-column forms where the heuristic matches the wrong block.
|
||||
Generating a filled preview requires loading the PDF into memory (via `@cantoo/pdf-lib`), modifying it, and either returning the bytes for streaming or writing to disk. Utah real estate forms (REPC, addendums) can be 15–30 pages and 2–8MB as raw PDFs. Running `PDFDocument.load()` on an 8MB PDF in a Vercel serverless function that has a 256MB memory limit can cause OOM errors under concurrent load. The Vercel function timeout (10s default, 60s max on Pro) can also be exceeded for large PDFs with many embedded fonts.
|
||||
|
||||
**Why it happens:**
|
||||
Auto-detection feels like the right UX goal — the agent should not have to manually place every field. PDF form field detection is non-trivial: some forms have AcroForm fields, some have visual annotation markers only, and some are flat scans. pdf-lib does not expose field dimensions/coordinates natively (a known open issue since 2020), so custom heuristics are required.
|
||||
Developers test with a small 2-page PDF in development and the function works fine. The function hits the memory wall only when a real Utah standard form (often 20+ pages with embedded images) is processed in production.
|
||||
|
||||
**How to avoid:**
|
||||
Treat auto-detection as a "best effort starting point," not a reliable system. The flow should be: (1) attempt AcroForm field parsing first — if the PDF has embedded AcroForm fields, use them; (2) if not, present a manual drag-to-place UI where Teressa visually positions signature and date fields on a PDF.js-rendered preview; (3) never send a document to a client without Teressa having confirmed field placements. This manual confirmation step is also a legal safeguard — it proves the agent reviewed the document before sending. Store field placements in PDF coordinates (bottom-left origin, points) in the database, not screen coordinates.
|
||||
Do not generate the preview inline in a serverless function on every request. Instead: generate the preview once (as a write operation), store the result in the `uploads/` directory or Vercel Blob, and serve it from there. The preview generation can be triggered on-demand (agent clicks "Generate Preview") and is idempotent. Set a timeout guard: if `PDFDocument.load()` takes longer than 8 seconds, return a 504 with "Preview temporarily unavailable." Monitor the Vercel function execution time and memory in the dashboard — alert at 70% of the memory limit.
|
||||
|
||||
**Warning signs:**
|
||||
- The heuristic search for signature fields is the only detection method, with no fallback UI.
|
||||
- Field placements are stored in screen pixels without converting to PDF coordinate space.
|
||||
- The app has never been tested with a scanned (non-OCR'd) PDF.
|
||||
- Preview is regenerated on every page load (no stored preview file).
|
||||
- The preview route calls `PDFDocument.load()` within a synchronous request handler.
|
||||
- Tests only use PDFs smaller than 2MB.
|
||||
|
||||
**Phase to address:**
|
||||
PDF field detection and form preparation UI — design the manual fallback first, then add auto-detection as an enhancement.
|
||||
Filled document preview phase — establish the "generate once, serve cached" pattern from the start.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 9: Font Not Embedded in PDF Before Flattening Causes Text to Disappear
|
||||
### Pitfall 9: Client Signing Page Confusion — Preview Shows Agent Pre-Fill but Client Signs a Different Document
|
||||
|
||||
**What goes wrong:**
|
||||
Teressa fills in text fields (property address, client name, price) on the PDF. When the form is flattened (fields baked into the page content stream), the text either disappears, renders as boxes, or uses a substitute font that looks wrong. This happens when the font referenced by the AcroForm field is not embedded in the PDF file and is not available in the server-side processing environment.
|
||||
|
||||
**Why it happens:**
|
||||
AcroForm fields reference fonts by name (e.g., "Helvetica"). On a desktop PDF viewer, these fonts are available system-wide and render correctly. On a headless server-side Node.js environment (e.g., Vercel serverless function), no system fonts are installed. When the field is flattened, the text cannot be rendered because the font is absent.
|
||||
The filled preview shows the document with all text pre-fills applied (client name, property address, price). The client signing page also renders the prepared PDF — which already contains those fills (because `prepare-document.ts` fills AcroForm fields and draws text onto the PDF). But the visual design difference between "this is a preview for review" and "this is the actual document you are signing" is unclear. If the agent generates a stale preview and the client signs a different (more recent) prepared PDF, the client believes they signed what they previewed, but the legal document has different content.
|
||||
|
||||
**How to avoid:**
|
||||
Before flattening any filled form, ensure all fonts referenced by form fields are embedded in the PDF. With pdf-lib, embed the font explicitly: load a standard font (e.g., StandardFonts.Helvetica from pdf-lib's built-in set) and set it on each form field before calling `form.flatten()`. For forms that use custom fonts, embed the TTF/OTF font file directly. Test flattening in the exact serverless environment where it will run (not locally on a Mac with system fonts installed).
|
||||
The client signing page must always serve the **same** prepared PDF that was cryptographically hashed at prepare time. The preview the agent saw must be generated from that exact file — not a re-generation. Store the SHA-256 hash of the prepared PDF at preparation time (same pattern as the existing `pdfHash` for signed PDFs). When serving the client's signing PDF, recompute and verify the hash matches before streaming. This ties the signed document back to the exact bytes the agent previewed.
|
||||
|
||||
**Warning signs:**
|
||||
- PDF flattening is tested only on a local development machine.
|
||||
- The serverless function environment has no system fonts installed and this has not been verified.
|
||||
- Filled form text is visible in the browser PDF preview (which uses browser fonts) but blank in the downloaded flattened PDF.
|
||||
- The preview is generated by a different code path than `prepare-document.ts` (e.g., a separate PDF rendering library).
|
||||
- No hash is stored for the prepared PDF, only for the signed PDF.
|
||||
- The agent can re-prepare after preview generation without the signing link being invalidated.
|
||||
|
||||
**Phase to address:**
|
||||
PDF form filling and flattening — must be validated in the production environment, not just locally.
|
||||
Filled document preview phase AND agent signing phase — hash the prepared PDF immediately after writing it (extend the existing `pdfHash` pattern from signed to prepared).
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 10: Mobile Signing UX Makes Canvas Signature Unusable
|
||||
### Pitfall 10: Agent Signature Field Handled by Client Signing Page
|
||||
|
||||
**What goes wrong:**
|
||||
Clients open the signing link on a mobile phone (the majority of email opens are on mobile). The signature canvas scrolls the page instead of capturing the drawing gesture. The canvas is too small to draw a legible signature. The "Submit" button is off-screen. The client gives up and calls Teressa to ask how to sign, creating friction that defeats the purpose of the email-link flow.
|
||||
A new `"agent-signature"` field type is added to `FieldPlacer`. The agent applies their saved signature to this field before sending. But `SigningPageClient.tsx` iterates all fields in `signatureFields` and shows a signing prompt for each one. If the agent-signature field is included in the array sent to the client, the client sees a field labeled "Signature" (or unlabeled) that is already visually signed with someone else's signature, and the progress bar counts it as an unsigned field the client must complete.
|
||||
|
||||
**Why it happens:**
|
||||
The signing page is designed and tested on desktop. The signature canvas uses a standard `<canvas>` element with touch event listeners added, but the browser's default touch behavior (page scroll) intercepts the gesture before the canvas can capture it. `touch-action: none` is not applied.
|
||||
The client signing page receives the full `signatureFields` array from the GET `/api/sign/[token]` response. The route currently returns `doc.signatureFields ?? []` without filtering. When agent-signature fields are added to the same array, they are included in the client's field list.
|
||||
|
||||
**Concrete location in codebase:**
|
||||
```typescript
|
||||
// /src/app/api/sign/[token]/route.ts, line 88
|
||||
signatureFields: doc.signatureFields ?? [],
|
||||
```
|
||||
This sends ALL fields to the client, including any agent-filled fields.
|
||||
|
||||
**How to avoid:**
|
||||
Apply `touch-action: none` on the signature canvas element to prevent the browser from intercepting touch gestures. Set the canvas to fill the full viewport width on mobile (100vw minus padding). Implement pinch-zoom prevention on the signing page only (meta viewport `user-scalable=no`) so accidental zoom does not distort the canvas. Test the complete signing flow on iOS Safari and Android Chrome — these are the two browsers with the most idiosyncratic touch handling. Consider using the `signature_pad` npm library (szimek/signature_pad) which handles touch normalization across devices. Display a "Draw your signature here" placeholder inside the canvas that disappears on first touch.
|
||||
Filter the `signatureFields` array in the signing token GET route: only return fields where `type !== 'agent-signature'` (or more precisely, only return fields the client is expected to sign). Agent-signed fields should be pre-embedded into the `preparedFilePath` PDF during document preparation — by the time the client opens the signing link, the agent's signature is already baked into the prepared PDF as a drawn image. The `signatureFields` array sent to the client should contain only the fields the client needs to provide.
|
||||
|
||||
**Warning signs:**
|
||||
- The signing page has only been tested on desktop Chrome.
|
||||
- The canvas element has no `touch-action` CSS property set.
|
||||
- A physical phone test shows the page scrolling instead of capturing strokes.
|
||||
- The full `signatureFields` array is returned from the signing token GET without filtering by `type`.
|
||||
- Agent-signed fields are stored in the same `signatureFields` JSONB column as client signature fields.
|
||||
- The client progress bar shows more fields than the client is responsible for signing.
|
||||
|
||||
**Phase to address:**
|
||||
Client-facing signing UI — mobile must be a primary test target from the first implementation, not a responsive afterthought.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 11: iOS Safari Canvas Self-Clearing and Vertical-Line Bug
|
||||
|
||||
**What goes wrong:**
|
||||
On iOS 15+, the signature canvas occasionally wipes itself mid-draw. On iOS 13, drawing vertical lines registers as a single point rather than a stroke — so signatures with vertical elements look like dots. These bugs are silent: the app appears to work, but the exported signature is illegible or empty, and that illegible image gets embedded in a legal document.
|
||||
|
||||
**Why it happens:**
|
||||
iOS 15 introduced a canvas self-clearing bug triggered by the Safari URL bar resizing the viewport while the user is drawing. The iOS 13 vertical stroke bug is a platform-level touch event regression. These do not reproduce in Chrome DevTools mobile emulation or on the iOS simulator — they require physical devices to discover.
|
||||
|
||||
**How to avoid:**
|
||||
- Use `signature_pad` version >= 1.0.6 (szimek/signature_pad) or `react-signature-canvas` >= 1.0.6 which includes the iOS 15 workaround
|
||||
- Set `preserveDrawingBuffer: true` on the canvas context when creating it to survive viewport resize events
|
||||
- Before accepting the submitted signature, validate that the canvas contains a meaningful number of non-background pixels — reject and prompt re-draw if the image is effectively blank
|
||||
- Export the canvas at 2x resolution (set `canvas.width` and `canvas.height` to 2x the CSS display size, then scale the 2D context by 2) so Retina displays produce a sharp embedded image
|
||||
- Test signing on physical iOS 13, iOS 15, and latest iOS devices — not in simulator
|
||||
|
||||
**Warning signs:**
|
||||
- Signed PDFs coming back from real clients have blank or near-blank signature images
|
||||
- The signature canvas library has not been updated in over a year
|
||||
- `preserveDrawingBuffer` is not explicitly set in the canvas initialization
|
||||
|
||||
**Phase to address:**
|
||||
Client-facing signing UI — physical device testing required before any real client document is sent.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 12: Signed PDFs Are Accessible via Guessable URLs (IDOR)
|
||||
|
||||
**What goes wrong:**
|
||||
An authenticated user (any agent or even a client who has signed their own document) modifies the document ID in the download URL — `/api/documents/1234/download` becomes `/api/documents/1233/download` — and successfully downloads another client's signed document. This is an Insecure Direct Object Reference (IDOR), consistently in the OWASP Top 10, and is a serious privacy violation in a real estate context where documents contain personal financial information.
|
||||
|
||||
**Why it happens:**
|
||||
The download route checks that the user is authenticated (has a valid session cookie) but does not verify that the authenticated user owns the requested document. Sequential integer IDs make enumeration trivial. This pattern is easy to miss because it requires testing with two separate accounts — single-account testing never reveals it.
|
||||
|
||||
**How to avoid:**
|
||||
- Use UUID v4 (not sequential integers) for all document IDs in URLs and API routes
|
||||
- In every API route handler that returns a document or its metadata, query the database to confirm `document.agent_id === session.agent_id` (or the equivalent ownership check) before streaming or returning the file
|
||||
- Do not rely solely on Next.js middleware for this check — middleware can be bypassed via CVE-2025-29927 (see Pitfall 13); put the ownership check inside the route handler itself
|
||||
- Store signed PDFs in a private object storage bucket (S3, Supabase Storage with RLS, Cloudflare R2) and generate short-lived pre-signed URLs (15 minutes or less) for downloads — never serve from a static public URL
|
||||
- Log every document download with the authenticated user's ID and timestamp for audit purposes
|
||||
|
||||
**Warning signs:**
|
||||
- Document download URLs contain sequential integers (e.g., `/documents/47`)
|
||||
- The download route handler does not perform a database ownership query before responding
|
||||
- PDFs are served from a publicly accessible storage bucket URL
|
||||
|
||||
**Phase to address:**
|
||||
Document storage and download API — UUID identifiers and ownership checks must be in place before any real client documents are uploaded.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 13: Next.js Middleware as the Only Authorization Gate (CVE-2025-29927)
|
||||
|
||||
**What goes wrong:**
|
||||
Authorization for sensitive routes (admin portal, document downloads, signing status) is implemented exclusively in Next.js middleware. An attacker adds the `x-middleware-subrequest` header to their HTTP request and bypasses middleware entirely, gaining unauthenticated access to protected routes. This is CVE-2025-29927, disclosed in March 2025, affecting all Next.js versions before 14.2.25 and 15.2.3.
|
||||
|
||||
**Why it happens:**
|
||||
Next.js middleware feels like the natural place to handle auth redirects. Developers add `middleware.ts` that checks for a session cookie and redirects unauthenticated users. This pattern is documented in the Next.js docs and is widely used — but the vulnerability demonstrated that middleware-only protection is not sufficient.
|
||||
|
||||
**How to avoid:**
|
||||
- Update Next.js to >= 14.2.25 or >= 15.2.3 immediately
|
||||
- Treat middleware as a first-layer UX redirect, not as a security enforcement layer
|
||||
- Add an explicit session/authorization check inside every API route handler and every server component that renders sensitive data — do not assume middleware has already validated the request
|
||||
- If deployed behind a reverse proxy (Nginx, Cloudflare, AWS ALB), configure it to strip the `x-middleware-subrequest` header from incoming requests
|
||||
- The rule: "Middleware redirects unauthenticated users; route handlers enforce authorization" — both layers must exist
|
||||
|
||||
**Warning signs:**
|
||||
- The `middleware.ts` file is the only place where session validation appears in the codebase
|
||||
- API routes do not individually check `getSession()` or `getToken()` before returning data
|
||||
- The Next.js version is older than 14.2.25
|
||||
|
||||
**Phase to address:**
|
||||
API route implementation — every route handler that touches client or document data must include an in-handler auth check regardless of middleware state.
|
||||
|
||||
---
|
||||
|
||||
### Pitfall 14: Credential-Based utahrealestate.com Scraping Has CFAA Exposure
|
||||
|
||||
**What goes wrong:**
|
||||
Using Teressa's utahrealestate.com credentials to automate form downloads is not just a Terms of Service question — it has Computer Fraud and Abuse Act (CFAA) exposure. A 2024 federal jury verdict (Ryanair vs. Booking.com, Delaware) found a scraping company violated the CFAA and established intent to defraud by accessing data behind a login wall, even using a third-party's credentials. The court further found that using techniques to avoid detection (rotating sessions, changing user-agent strings) separately supported the "intent to defraud" element.
|
||||
|
||||
**Why it happens:**
|
||||
Developers treat "using your own credentials" as inherently authorized. But CFAA analysis asks whether the automated programmatic use exceeds the authorized use defined in the ToS, not just whether credentials are valid. The 2022 hiQ v. LinkedIn ruling protecting public data scraping explicitly does not extend to data behind authentication barriers.
|
||||
|
||||
**How to avoid:**
|
||||
- Do not write automated credential-based scraping code for utahrealestate.com under any circumstances without a written API or data agreement from the platform
|
||||
- The correct architecture: Teressa manually downloads needed form PDFs from utahrealestate.com and uploads them into the app's admin portal. This is a one-time-per-form-update action, not per-client.
|
||||
- For state-approved real estate forms, the Utah Division of Real Estate publishes them at commerce.utah.gov/realestate — these are public domain and safe to bundle directly in the app
|
||||
- If programmatic access is genuinely needed, contact utahrealestate.com to request a formal data agreement or API key before writing any code
|
||||
|
||||
**Warning signs:**
|
||||
- Any code in the repository that performs an automated login to utahrealestate.com
|
||||
- Teressa's MLS password is stored anywhere in the application's configuration or database
|
||||
- The app has hardcoded selectors or URLs pointing to utahrealestate.com form library paths
|
||||
|
||||
**Phase to address:**
|
||||
Forms import architecture — this decision must be made and documented before any integration code is written.
|
||||
Agent signing phase — filter the signing response by field type before the first agent-signed document is sent to a client.
|
||||
|
||||
---
|
||||
|
||||
@@ -324,16 +246,12 @@ Shortcuts that seem reasonable but create long-term problems.
|
||||
|
||||
| Shortcut | Immediate Benefit | Long-term Cost | When Acceptable |
|
||||
|----------|-------------------|----------------|-----------------|
|
||||
| Storing Teressa's utahrealestate.com credentials in `.env` for scraping | Avoids building a manual upload flow | ToS violation; breaks when site changes; security liability if env is leaked | Never — use manual PDF upload instead |
|
||||
| Using client-side timestamp for signing event | Simpler code, no server roundtrip | Timestamps are falsifiable; legally worthless as audit evidence | Never for legal audit trail |
|
||||
| Skipping PDF coordinate conversion unit tests | Faster initial development | Silent placement bugs on real-world forms; discovered only when a client signs in the wrong place | Never — write the test before building the UI |
|
||||
| Stateless JWT as signing token with no DB revocation | Simpler auth, no DB lookup on link access | Tokens cannot be invalidated; replay attacks are undetectable | Never for one-time signing links |
|
||||
| Hardcoding IDX disclaimer text in a component | Quick to ship | Disclaimer text changes with NAR policy; requires code deploy to update | Acceptable only if a content management hook is added in the next phase |
|
||||
| Full listing re-fetch instead of delta sync | Simpler initial implementation | Rate limit exhaustion; slow sync; stale off-market listings | Acceptable in Phase 1 dev/test only, must be replaced before public launch |
|
||||
| Skipping form flattening (storing unflattenend PDFs with live AcroForm fields) | Faster PDF processing | Signed documents can be edited after the fact in any PDF editor; legally indefensible | Never for final signed documents |
|
||||
| Using sequential integer IDs for document URLs | Simpler schema | IDOR vulnerability — any user can enumerate other clients' documents by changing the number in the URL | Never — use UUID v4 |
|
||||
| Putting all auth logic in Next.js middleware only | Less code per route | CVE-2025-29927 bypass allows unauthenticated access by adding a single HTTP header | Never — always add in-handler auth checks |
|
||||
| Skipping physical iOS device testing for signature canvas | Faster QA iteration | iOS 13 vertical stroke bug and iOS 15 canvas self-clearing are invisible in simulator | Never for any client-facing signing release |
|
||||
| Store saved signature as dataURL in users table | No new file storage code needed | Every user query pulls 15–60KB of base64; dataURL exposed in client props | Never — use file storage from the start |
|
||||
| Re-use same `_prepared.pdf` path for preview and final prepared doc | No versioning logic needed | Stale previews; no way to prove which prepared PDF the client signed | Never — versioned paths required for legal integrity |
|
||||
| Return all signatureFields to client (no type filtering) | Simpler route code | Client sees agent-signature fields as required fields to complete | Never for agent-signature type; acceptable for debugging only |
|
||||
| Prompt OpenAI with entire PDF as one request | Simpler prompt code | Fails silently on documents > ~8 pages; token limit hit without hard error | Acceptable only for prototyping with < 5 page test PDFs |
|
||||
| Add `type` to SignatureFieldData but don't add a schema migration | Skip Drizzle migration step | Existing rows have `null` type; `signatureFields` JSONB array has mixed null/typed entries; TypeScript union breaks | Never — migrate immediately |
|
||||
| Generate preview on every page load | No caching logic needed | OOM errors on large PDFs under Vercel memory limit; slow UX | Acceptable only during local development |
|
||||
|
||||
---
|
||||
|
||||
@@ -343,14 +261,12 @@ Common mistakes when connecting to external services.
|
||||
|
||||
| Integration | Common Mistake | Correct Approach |
|
||||
|-------------|----------------|------------------|
|
||||
| WFRMLS RESO API | Using `$filter` with wrong field name casing (e.g., `modificationtimestamp` instead of `ModificationTimestamp`) — all field names are case-sensitive | Always copy field names exactly from the RESO metadata endpoint response; do not guess or lowercase |
|
||||
| WFRMLS RESO API | Expecting sold/closed price data in IDX feed | Utah is a non-disclosure state; closed price data is not available in IDX feeds; design UI without sale price history |
|
||||
| WFRMLS RESO API | Assuming instant vendor credential provisioning | Vendor approval requires contract signing, background check, and compliance review; allow 2–4 weeks lead time |
|
||||
| utahrealestate.com forms | Automating PDF downloads with Teressa's session cookies | Terms of Service prohibit automated data extraction; use manual upload or state-published public forms |
|
||||
| Email delivery (signing links) | Relying on `localhost` or unverified sender domain for transactional email | Signing emails go to spam; SPF/DKIM/DMARC must be configured on teressacopelandhomes.com before first client send |
|
||||
| pdf-lib form flattening | Calling `form.flatten()` without first embedding fonts | Text fields render blank or with substitute fonts on server-side; embed fonts explicitly before flattening |
|
||||
| pdf-lib signature image placement | Using screen/canvas Y coordinate directly as the pdf-lib Y value | PDF origin is bottom-left; screen origin is top-left — always apply `pdfY = page.getHeight() - (canvasY / viewportScale)` |
|
||||
| Document download API | Checking authentication only ("is logged in?") rather than authorization ("does this user own this document?") | Perform an ownership database query inside the route handler; use UUID document IDs; serve from private storage with short-lived signed URLs |
|
||||
| OpenAI Vision API | Sending raw PDF bytes — PDFs are not natively supported by vision models | Convert each page to PNG via pdfjs-dist on the server; send page images, not PDF bytes |
|
||||
| OpenAI structured output | Using `response_format: { type: 'json_object' }` and hoping the schema matches | Use `response_format: { type: 'json_schema', json_schema: { ... } }` with the exact schema, then validate with Zod |
|
||||
| `@cantoo/pdf-lib` (confirmed import in codebase) | Calling `embedPng()` with a base64 dataURL that includes the `data:image/png;base64,` prefix on systems that strip it | The existing `embed-signature.ts` already handles this correctly — preserve the pattern when adding new embed paths |
|
||||
| `@cantoo/pdf-lib` flatten | Flattening before drawing rectangles causes AcroForm overlay to appear on top of drawn content | The existing `prepare-document.ts` already handles order correctly (flatten first, then draw) — preserve this order in any new prepare paths |
|
||||
| Vercel Blob (if migrated from local uploads) | Fetching a Blob URL inside a serverless function on the same Vercel deployment causes a request to the CDN with potential cold-start latency | Use the `@vercel/blob` SDK's `get()` method rather than `fetch(blob.url)` from within API routes |
|
||||
| Agent signature file serving | Serving the agent's saved signature PNG via a public URL | Gate all signature file access behind the authenticated agent API — never expose with a public Blob URL |
|
||||
|
||||
---
|
||||
|
||||
@@ -360,10 +276,10 @@ Patterns that work at small scale but fail as usage grows.
|
||||
|
||||
| Trap | Symptoms | Prevention | When It Breaks |
|
||||
|------|----------|------------|----------------|
|
||||
| Full WFRMLS listing re-fetch on every sync | Slow sync jobs; API rate limit errors; listings temporarily unavailable during fetch | Implement delta sync using `ModificationTimestamp` filter | At ~100+ listings or hourly sync schedule |
|
||||
| Generating signed PDFs synchronously in an API route | Signing page times out; Vercel/Next.js 10s function timeout exceeded | Move PDF generation to a background job or streaming response | On PDFs larger than ~2MB or with complex form filling |
|
||||
| Storing signed PDFs in the Next.js app's local filesystem | PDFs lost on serverless deployment; no persistence across function instances | Store all documents in S3-compatible object storage (e.g., AWS S3, Cloudflare R2) from day one | On first serverless deployment |
|
||||
| Loading full-page PDF.js bundle on every page of the site | Slow initial page load on the public marketing site | Code-split the PDF viewer — load it only on the agent dashboard and signing page routes | Immediately — affects all visitors |
|
||||
| OpenAI call inline with agent "AI Place Fields" button click | 10–30 second page freeze; API timeout on multi-page PDFs | Trigger AI placement as a background job; poll for completion; show progress bar | Immediately on PDFs > 5 pages |
|
||||
| PDF preview generation in a synchronous serverless function | Vercel function timeout (60s max Pro); OOM on 8MB PDFs | Generate once and store; serve from storage | On PDFs > 10MB or under concurrent load |
|
||||
| Storing all signatureFields JSONB on documents table without a size guard | Large JSONB column slows document list queries | Add a field count limit (max 50 fields); if AI places more, require agent review | When AI places fields on 25+ page documents with many fields per page |
|
||||
| dataURL signature image in `signaturesRef.current` in SigningPageClient | Each re-render serializes 50KB+ per signature into JSON | Already handled correctly in v1.0 (ref, not state) — do not move signature data to state when adding type-based rendering | Would break at > 5 simultaneous signature fields |
|
||||
|
||||
---
|
||||
|
||||
@@ -373,15 +289,11 @@ Domain-specific security issues beyond general web security.
|
||||
|
||||
| Mistake | Risk | Prevention |
|
||||
|---------|------|------------|
|
||||
| Signing link token is not hashed at rest in the database | Database breach exposes all active signing tokens; attacker can sign documents on behalf of clients | Store HMAC-SHA256(token) in DB; compare hash on request, never the raw token |
|
||||
| No rate limit on signing link generation | Attacker floods Teressa's client email inboxes with signing emails from the app | Rate limit signing link creation per document and per agent session (max 3 resends per document) |
|
||||
| Signed PDFs accessible via guessable URL (e.g., `/documents/123`) | Sequential ID enumeration lets anyone download any signed document | Use unguessable UUIDs for document storage paths and signed URLs with short TTL for downloads |
|
||||
| Agent session not invalidated on agent logout | If Teressa leaves a browser tab open on a shared computer, her entire client document library is accessible | Implement server-side session invalidation on logout; do not rely on client-side cookie deletion alone |
|
||||
| Signature canvas accepts 0-stroke "signatures" | Client submits a blank canvas as their signature; the signed document has no visible signature mark | Validate that the canvas has at least a minimum number of non-white pixels / stroke events before accepting submission |
|
||||
| No CSRF protection on the signature submission endpoint | Cross-site request forgery could submit a forged signature | The signing token in the URL/body acts as an implicit CSRF token only if validated server-side on every request; add `SameSite=Strict` to any session cookies |
|
||||
| Signed PDFs at predictable/guessable URLs (sequential integer IDs) | Any authenticated user can enumerate and download all signed documents; real estate documents contain SSNs, financial info, home addresses | Use UUID v4 for document IDs; store in private bucket; issue short-lived pre-signed download URLs per-request |
|
||||
| Next.js middleware as sole authorization layer | CVE-2025-29927 allows middleware bypass with a crafted HTTP header; attacker accesses any protected route without authentication | Upgrade Next.js to >= 14.2.25; add ownership check in every API route handler independent of middleware |
|
||||
| Blank canvas accepted as a valid signature | Empty or near-empty PNG gets embedded in the signed document as the client's legally binding signature | Server-side validation: reject submissions where the canvas image has fewer than a minimum number of non-background pixels or fewer than a minimum number of recorded stroke events |
|
||||
| Agent saved signature served via a predictable or public file path | Any user who can guess the path downloads the agent's legal signature | Store under a UUID path; serve only through `GET /api/agent/signature` which verifies the better-auth session before streaming |
|
||||
| AI field placement values (pre-fill text) passed to OpenAI without scrubbing | Client PII (name, email, SSN, property address) sent to OpenAI and stored in their logs | Provide only anonymized document structure to the AI (page images without personally identifiable pre-fill values); apply pre-fill values server-side after AI field detection |
|
||||
| Preview PDF served at a guessable URL (e.g. `/api/documents/{id}/preview`) without auth check | Anyone with the document ID can download a prepared document containing client PII | All document file routes must verify the agent session before streaming — apply the same guard as the existing `/api/documents/[id]/download/route.ts` |
|
||||
| Agent signature dataURL transmitted from client to server in an unguarded API route | Any authenticated user (if multi-agent is ever added) can overwrite the saved signature | The save-signature endpoint must verify the session user matches the signature owner — prepare for this even in solo-agent v1 |
|
||||
| Signed PDF stale preview served to client after re-preparation | Client signs a document that differs from what agent reviewed and approved | Hash prepared PDF at prepare time; verify hash before serving to client signing page |
|
||||
|
||||
---
|
||||
|
||||
@@ -391,12 +303,12 @@ Common user experience mistakes in this domain.
|
||||
|
||||
| Pitfall | User Impact | Better Approach |
|
||||
|---------|-------------|-----------------|
|
||||
| Showing "Invalid token" when a signing link is expired | Client calls Teressa confused; does not know what to do next | Show "This signing link has expired. Contact Teressa Copeland to request a new one." with her phone/email pre-filled |
|
||||
| Showing "Invalid token" when a signing link has already been used | Client thinks something is broken; may attempt to sign again | Show "You have already signed this document. Teressa has your signed copy." with a reassuring message |
|
||||
| Forcing client to scroll through the entire document before the "Sign" button appears | Clients on mobile give up before reaching the signature field | Provide a sticky "Jump to Signature" button; do not hide the sign action behind mandatory scrolling |
|
||||
| No confirmation screen after signing | Client does not know if the submission worked | Show a clear "Document Signed Successfully" confirmation with the document name and a timestamp; optionally send a confirmation email |
|
||||
| Agent has no way to see which documents are awaiting signature vs. completed | Teressa loses track of unsigned documents | Dashboard must show document status at a glance: Draft / Sent / Signed / Archived — never mix them in a flat list |
|
||||
| Client-facing signing page has Teressa's agent branding (logo, nav) mixed with client-action UI | Client is confused about whether they are on Teressa's site or a signing service | Keep the signing page minimal: document title, Teressa's name as sender, the document, the signature canvas, and a submit button — no site navigation |
|
||||
| Preview opens in a new browser tab as a raw PDF | Agent has no context that this is a preview vs. the final document; no field overlays visible | Display preview in-app with a "PREVIEW — Fields Filled" watermark overlay on each page |
|
||||
| AI-placed fields shown without a review step | Agent sends a document with misaligned AI fields to a client; client is confused by floating sign boxes | AI placement populates the FieldPlacer UI for agent review — never auto-sends; agent must manually click "Looks good, proceed" |
|
||||
| "Prepare and Send" button available before the agent has placed any fields | Agent sends a blank document with no signature fields; client has nothing to sign | Disable "Prepare and Send" if `signatureFields` is empty or contains only agent-signature fields (no client fields) |
|
||||
| Agent saved signature is applied but no visual confirmation is shown | Agent thinks the signature was applied; document arrives unsigned because the apply step silently failed | Show the agent's saved signature PNG in the field placer overlay immediately after apply; require explicit confirmation before the prepare step |
|
||||
| Preview shows pre-filled text but not field type labels | Agent cannot distinguish a "checkbox" pre-fill from a "text" pre-fill in the visual preview | Show field type badges (small colored labels) on the preview overlay, not just the filled content |
|
||||
| Client signing page shows no progress for non-signature fields (text, checkbox, date) | Client doesn't know they need to fill in text boxes or check checkboxes — sees only signature prompts | The progress bar in `SigningProgressBar.tsx` counts `signatureFields.length` — this must count all client-facing fields, not just signature-type fields |
|
||||
|
||||
---
|
||||
|
||||
@@ -404,20 +316,14 @@ Common user experience mistakes in this domain.
|
||||
|
||||
Things that appear complete but are missing critical pieces.
|
||||
|
||||
- [ ] **E-signature capture:** Signing canvas works in Chrome desktop — verify it captures correctly on iOS Safari and Android Chrome with touch gestures (not scroll events).
|
||||
- [ ] **PDF signing:** Signature appears in the browser PDF preview — verify the position is correct in the actual downloaded PDF file opened in Adobe Reader or Preview.
|
||||
- [ ] **Audit trail:** Events are logged — verify the log includes: email sent, link opened, document viewed, signature submitted, final PDF hash, all with server-side timestamps.
|
||||
- [ ] **Signing link security:** Link opens the signing page — verify the link cannot be used a second time after signing and expires after 72 hours even if unused.
|
||||
- [ ] **Font flattening:** Form fields fill correctly locally — verify the filled and flattened PDF looks correct when generated in the production serverless environment (not just on a Mac with system fonts).
|
||||
- [ ] **IDX compliance:** Listings display on the public site — verify every listing card and detail page has: listing broker attribution, MLS disclaimer text, last updated timestamp, and buyer's agent compensation field per 2024 NAR settlement.
|
||||
- [ ] **Stale listings:** Sync job runs — verify the job uses `ModificationTimestamp` delta filtering and removes off-market listings within 1 hour of status change, not at next full refresh.
|
||||
- [ ] **PDF coordinate placement:** Signature field drag-and-drop works on a blank PDF — verify placement coordinates are correct on an actual Utah real estate purchase agreement form, including any rotated pages.
|
||||
- [ ] **Document storage:** PDFs save — verify signed PDFs are stored in persistent object storage (S3/R2), not the local filesystem, and that URLs survive a fresh deployment.
|
||||
- [ ] **Document IDOR:** Document download works for the document owner — verify with a second test account that changing the document ID in the URL returns a 403 or 404, not the other account's document.
|
||||
- [ ] **iOS canvas bugs:** Signing canvas works on Chrome desktop — verify on a physical iPhone (iOS 13 and iOS 15+) in Safari that vertical strokes register correctly and the canvas does not clear itself during drawing.
|
||||
- [ ] **Blank signature rejection:** Submitting a signature works — verify that clicking Submit on an untouched blank canvas returns a validation error rather than producing a signed document with an empty signature image.
|
||||
- [ ] **Next.js auth:** Route protection appears to work via middleware — verify that every sensitive API route handler contains its own session/ownership check independent of middleware by testing with a crafted request that bypasses middleware.
|
||||
- [ ] **CFAA compliance:** Forms import works — verify there is no code in the repository that performs automated login to utahrealestate.com; all forms must enter the system via manual agent upload.
|
||||
- [ ] **AI field placement:** Verify the coordinate conversion unit test asserts specific PDF-space x/y values (not just "fields are returned") — eyeball testing will miss Y-axis inversion errors on Utah standard forms.
|
||||
- [ ] **Expanded field types:** Verify `SigningPageClient.tsx` has a rendering branch for every type in the `SignatureFieldData` type union — not just the new FieldPlacer palette tokens. Check for the default/fallback case.
|
||||
- [ ] **Agent saved signature:** Verify the saved signature is stored as a file path, not a dataURL TEXT column — check the Drizzle schema migration and confirm no `dataUrl` column was added to `users`.
|
||||
- [ ] **Agent signs first:** Verify that after agent applies their signature, the agent-signature field is embedded into the prepared PDF and removed from the `signatureFields` array that gets sent to the client — not just visually hidden in the FieldPlacer.
|
||||
- [ ] **Filled preview:** Verify the preview URL changes when fields or text fill values change (cache-busting via timestamp or hash in the path) — open DevTools network tab, modify a field, re-generate preview, confirm a new file is fetched.
|
||||
- [ ] **Filled preview freshness gate:** Verify the "Send" button is disabled when `lastPreviewGeneratedAt < lastFieldsUpdatedAt` — test by generating a preview, changing a field, and confirming the send button becomes disabled.
|
||||
- [ ] **OpenAI token limit:** Verify the AI placement works on a real 20-page Utah REPC form, not just a 2-page test PDF — check that page 15+ fields are detected with the same accuracy as page 1.
|
||||
- [ ] **Schema migration:** Verify that documents created in v1.0 (where `signatureFields` JSONB has entries without a `type` key) are handled gracefully by all v1.1 code paths — add a null-safe fallback for `field.type ?? 'signature'` throughout.
|
||||
|
||||
---
|
||||
|
||||
@@ -427,13 +333,12 @@ When pitfalls occur despite prevention, how to recover.
|
||||
|
||||
| Pitfall | Recovery Cost | Recovery Steps |
|
||||
|---------|---------------|----------------|
|
||||
| Signed PDF lacks document hash; legal challenge | HIGH | Reconstruct signing ceremony evidence from audit log events, IP records, email delivery logs; engage legal counsel; this cannot be fully recovered — prevention is the only answer |
|
||||
| utahrealestate.com scraping blocked or ToS violation notice | MEDIUM | Immediately disable scraper; switch to manual PDF upload flow; apologize to MLS; a manually-uploaded forms library was the right design from the start |
|
||||
| IDX compliance violation discovered (missing attribution) | MEDIUM | Fix attribution immediately across all listing pages; contact WFRMLS compliance to self-report before they audit; document the fix with timestamps |
|
||||
| Signing link replay attack (duplicate signature submitted) | MEDIUM | Add `used_at` validation to token check; mark the earlier duplicate submission as invalid in the audit log; notify affected signer via email |
|
||||
| Signatures misplaced in PDFs (coordinate bug) | MEDIUM | Identify all affected documents; mark them as invalid in the system; Teressa must re-send for re-signing; fix coordinate conversion and add the regression test |
|
||||
| PDF font flattening failure (blank text in signed PDFs) | LOW-MEDIUM | Embed standard fonts explicitly in the flattening step; regenerate affected documents; re-send for signing if clients have not yet signed |
|
||||
| Stale listing shown to client who made an offer on unavailable property | LOW | Implement hourly delta sync immediately; add a "verify availability" disclaimer to listing pages in the short term |
|
||||
| Client received signing link but signing page crashes on new field types | HIGH | Emergency hotfix: add `field.type ?? 'signature'` fallback in SigningPageClient; deploy; invalidate old token; send new link |
|
||||
| AI placed fields are wrong/inverted on first real-form test | LOW | Fix coordinate conversion unit; re-run AI placement for that document; no data migration needed |
|
||||
| Agent saved signature stored as dataURL in DB | MEDIUM | Add migration: extract dataURL to file, update path column, nullify dataURL column; existing signed PDFs are unaffected |
|
||||
| Preview PDF served stale after field changes | LOW | Add cache-busting query param or timestamp to preview URL; no data changes needed |
|
||||
| Agent-signature field appears in client's signing field list | HIGH | Emergency hotfix: filter signatureFields in signing token GET by type; redeploy; affected in-flight signing sessions may need new tokens |
|
||||
| Large PDF causes Vercel function OOM during preview generation | MEDIUM | Switch preview to background job + polling; no data migration; existing prepared PDFs are valid |
|
||||
|
||||
---
|
||||
|
||||
@@ -443,68 +348,34 @@ How roadmap phases should address these pitfalls.
|
||||
|
||||
| Pitfall | Prevention Phase | Verification |
|
||||
|---------|------------------|--------------|
|
||||
| No tamper-evident PDF hash | Document signing backend | Compute and store SHA-256 hash on every signed PDF; verify by recomputing and comparing |
|
||||
| Incomplete audit trail | Email-link signing flow | Trace a full test signing ceremony and confirm all 6 event types appear in the audit log |
|
||||
| Replayable signing link | Email-link signing flow | After signing, reload the signing URL and confirm it returns "already signed" — not the canvas |
|
||||
| PDF coordinate mismatch | PDF field placement UI | Place a test signature on a known position in an actual Utah purchase agreement; open in Acrobat and verify visual position |
|
||||
| utahrealestate.com forms scraping | Forms import architecture | No scraping code exists in the codebase; PDF source is manual upload or public state forms only |
|
||||
| IDX display without required attribution | Public listings feature | Audit every listing page template against NAR IDX policy checklist before launch |
|
||||
| Stale off-market listings | Listings sync infrastructure | Manually mark a test listing off-market in a dev MLS feed; verify it disappears from the site within 1 hour |
|
||||
| PDF heuristic field detection failure | PDF form preparation UI | Test against a scanned (non-OCR) Utah real estate form; verify the manual placement fallback UI appears |
|
||||
| Font not embedded before flattening | PDF form filling | Flatten a filled form in the production serverless environment; open the result in Acrobat and confirm all text is visible |
|
||||
| Mobile canvas unusable | Client signing UI | Complete a full signing flow on iOS Safari and Android Chrome on physical devices |
|
||||
| Missing MLS compliance disclaimers | Public listings feature | Legal/compliance checklist as a PR gate for any listing-display component |
|
||||
| iOS canvas self-clearing / vertical stroke bug | Client signing UI | Physical device testing on iOS 13 and iOS 15+ before any client-facing release |
|
||||
| Signed PDF IDOR (sequential IDs, no ownership check) | Document storage architecture | Penetration test with two accounts: confirm ID enumeration returns 403, not another user's document |
|
||||
| Next.js middleware-only auth (CVE-2025-29927) | API route implementation | Code review gate: every sensitive route handler must contain an explicit ownership/session check |
|
||||
| CFAA exposure from credential-based scraping | Forms import architecture design | Architecture decision document: no automated login code for utahrealestate.com; manual upload only |
|
||||
| Breaking signing page with new field types (Pitfall 1) | Phase 1: Schema + signing page update | Deploy field type union; confirm signing page renders placeholder for unknown types; load an old v1.0 document with no type field and verify graceful fallback |
|
||||
| AI coordinate system mismatch (Pitfall 2) | Phase 2: AI integration — coordinate conversion utility | Unit test with a known Utah REPC: assert specific PDF-space x/y for a known field; Y-axis inversion test |
|
||||
| OpenAI token limits on large PDFs (Pitfall 3) | Phase 2: AI integration — page-by-page pipeline | Test with the longest form Teressa uses (likely 20+ page REPC); verify all pages processed |
|
||||
| Prompt hallucination and schema incompatibility (Pitfall 4) | Phase 2: AI integration — Zod validation of AI response | Feed an edge-case page (all text, no form fields) and verify AI returns empty array, not hallucinated fields |
|
||||
| Saved signature as dataURL in DB (Pitfall 5) | Phase 3: Agent saved signature | Confirm Drizzle schema has a path column, not a dataURL column; verify file is stored under UUID path |
|
||||
| Race condition: agent updates signature mid-signing (Pitfall 6) | Phase 3: Agent saved signature + supersede flow | Confirm "Prepare and Send" on a Sent/Viewed document requires confirmation and invalidates old token |
|
||||
| Stale preview after field changes (Pitfall 7) | Phase 4: Filled document preview | Modify a field after preview generation; confirm send button disables or preview refreshes |
|
||||
| OOM on large PDF preview (Pitfall 8) | Phase 4: Filled document preview | Test preview generation on a 20-page REPC; monitor Vercel function memory in dashboard |
|
||||
| Client signs different doc than agent previewed (Pitfall 9) | Phase 4: Filled document preview | Confirm prepared PDF is hashed at prepare time; verify hash is checked before streaming to client |
|
||||
| Agent-signature field shown to client (Pitfall 10) | Phase 3: Agent signing flow | Confirm signing token GET filters `type === 'agent-signature'` fields before returning; test with a document that has both agent and client signature fields |
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
|
||||
- [ESIGN Act & UETA Compliance — UnicornForms](https://www.unicornforms.com/blog/esign-ueta-compliance)
|
||||
- [E-Signature Audit Trail Schema — Anvil Engineering](https://www.useanvil.com/blog/engineering/e-signature-audit-trail-schema-events-json-checklist/)
|
||||
- [Using E-Signatures in Court — Fenwick](https://www.fenwick.com/insights/publications/using-e-signatures-in-court-the-value-of-an-audit-trail)
|
||||
- [E-Signature Software with Audit Trail — Zignt](https://zignt.com/blog/e-signature-software-with-audit-trail)
|
||||
- [Utah Electronic Signature Act Explained — Signable](https://www.signable.co.uk/electronic-signatures-legally-binding-utah/)
|
||||
- [UtahRealEstate.com Data Services — Vendor FAQ](https://vendor.utahrealestate.com/faq)
|
||||
- [UtahRealEstate.com RESO Web API Docs](https://vendor.utahrealestate.com/webapi/docs/tuts/endpoints)
|
||||
- [WFRMLS IDX — Utah MLS at RESO](https://www.reso.org/web-api-examples/mls/utah-mls/)
|
||||
- [UtahRealEstate Partners with SkySlope Forms](https://blog.utahrealestate.com/index.php/2022/01/31/utahrealestate-comskyslope/)
|
||||
- [IDX Integration Best Practices — Real Estate 7](https://contempothemes.com/idx-integration-best-practices-for-mls-rules/)
|
||||
- [NAR IDX Policy Statement 7.58](https://www.nar.realtor/handbook-on-multiple-listing-policy/advertising-print-and-electronic-section-1-internet-data-exchange-idx-policy-policy-statement-7-58)
|
||||
- [Summary of 2025 MLS Changes — NAR](https://www.nar.realtor/about-nar/policies/summary-of-2025-mls-changes)
|
||||
- [PDF Page Coordinates — pdfscripting.com](https://www.pdfscripting.com/public/PDF-Page-Coordinates.cfm)
|
||||
- [PDF Coordinate Systems — Apryse](https://apryse.com/blog/pdf-coordinates-and-pdf-processing)
|
||||
- [Critical Bug: PDF Annotation Positioning Mismatch — sign-pdf GitHub issue](https://github.com/mattsilv/sign-pdf/issues/1)
|
||||
- [pdf-lib Field Coordinates Feature Request — GitHub issue #602](https://github.com/Hopding/pdf-lib/issues/602)
|
||||
- [Magic Link Security Best Practices — Deepak Gupta](https://guptadeepak.com/mastering-magic-link-security-a-deep-dive-for-developers/)
|
||||
- [Passwordless Magic Links: UX and Security Checklist — AppMaster](https://appmaster.io/blog/passwordless-magic-links-ux-security-checklist)
|
||||
- [Magic Link Security — BayTech Consulting](https://www.baytechconsulting.com/blog/magic-links-ux-security-and-growth-impacts-for-saas-platforms-2025)
|
||||
- [Signature Pad Library — szimek/signature_pad](https://github.com/szimek/signature_pad)
|
||||
- [MLS Listing Data Freshness and Cache — MLSImport](https://mlsimport.com/fix-outdated-listings-on-your-wordpress-real-estate-site/)
|
||||
- [Utah DRE State-Approved Forms — commerce.utah.gov](https://commerce.utah.gov/realestate/real-estate/forms/state-approved/)
|
||||
- [Canvas clears itself on iOS 15 (react-signature-canvas Issue #65) — GitHub](https://github.com/agilgur5/react-signature-canvas/issues/65)
|
||||
- [Drawing broken on iOS 13 (signature_pad Issue #455) — GitHub](https://github.com/szimek/signature_pad/issues/455)
|
||||
- [Free drawing on iOS disables page scrolling (fabric.js Issue #3756) — GitHub](https://github.com/fabricjs/fabric.js/issues/3756)
|
||||
- [Insecure Direct Object Reference Prevention — OWASP Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html)
|
||||
- [IDOR in Next.js / JavaScript Applications — nodejs-security.com](https://www.nodejs-security.com/blog/insecure-direct-object-reference-idor-javascript-applications)
|
||||
- [CVE-2025-29927: Next.js Middleware Authorization Bypass — ProjectDiscovery](https://projectdiscovery.io/blog/nextjs-middleware-authorization-bypass)
|
||||
- [Critical Next.js Vulnerability: Authorization Bypass in Middleware — Jit.io](https://www.jit.io/resources/app-security/critical-nextjs-vulnerability-authorization-bypass-in-middleware)
|
||||
- [U.S. Court Rules Against Online Travel Booking Company in Web-Scraping Case (2024 CFAA jury verdict) — Alston Bird](https://www.alstonprivacy.com/u-s-court-rules-against-online-travel-booking-company-in-web-scraping-case/)
|
||||
- [Web Scraping, website terms and the CFAA: hiQ affirmed — White & Case](https://www.whitecase.com/insight-our-thinking/web-scraping-website-terms-and-cfaa-hiqs-preliminary-injunction-affirmed-again)
|
||||
- [The Computer Fraud and Abuse Act and Third-Party Web Scrapers — Finnegan](https://www.finnegan.com/en/insights/articles/the-computer-fraud-and-abuse-act-and-third-party-web-scrapers.html)
|
||||
- [JWT Security Best Practices — Curity](https://curity.io/resources/learn/jwt-best-practices/)
|
||||
- [JSON Web Token Attacks — PortSwigger Web Security Academy](https://portswigger.net/web-security/jwt)
|
||||
- [PDF Form Flattening — DynamicPDF](https://www.dynamicpdf.com/docs/dotnet/dynamic-pdf-form-flattening)
|
||||
- [Flatten Your PDFs for Court Filings — eFilingHelp](https://www.efilinghelp.com/electronic-filing/flatten-pdfs/)
|
||||
- [E-Signatures in Real Estate Transactions: a Deep Dive — Gomez Law, APC](https://gomezlawla.com/blog/e-signatures-in-real-estate-transactions-a-deep-dive/)
|
||||
- [Enforceability of Electronic Agreements in Real Estate — Arnall Golden Gregory LLP](https://www.agg.com/news-insights/publications/enforceability-of-electronic-agreements-in-real-estate-transactions-06-30-2016/)
|
||||
- [Utah Code § 46-4-201: Legal recognition of electronic signatures — Justia](https://law.justia.com/codes/utah/title-46/chapter-4/part-2/section-201/)
|
||||
- [Utah Code § 46-4-203: Attribution and effect of electronic signature — Justia](https://law.justia.com/codes/utah/2023/title-46/chapter-4/part-2/section-203/)
|
||||
- Reviewed `src/lib/db/schema.ts` — `SignatureFieldData` has no `type` field; confirmed by inspection 2026-03-21
|
||||
- Reviewed `src/app/sign/[token]/_components/SigningPageClient.tsx` — confirmed all fields open signature modal; no type branching
|
||||
- Reviewed `src/app/portal/(protected)/documents/[docId]/_components/FieldPlacer.tsx` — confirmed single "Signature" token; `screenToPdfCoords` function confirms Y-axis inversion pattern
|
||||
- Reviewed `src/lib/signing/embed-signature.ts` — confirms `@cantoo/pdf-lib` import; PNG-only embed
|
||||
- Reviewed `src/lib/pdf/prepare-document.ts` — confirms AcroForm flatten-first ordering; text stamp fallback
|
||||
- Reviewed `src/app/api/sign/[token]/route.ts` — confirmed `signatureFields: doc.signatureFields ?? []` sends unfiltered fields to client (line 88)
|
||||
- Reviewed `src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx` — no guard against re-preparation of Sent/Viewed documents
|
||||
- [OpenAI Vision API Token Counting](https://platform.openai.com/docs/guides/vision#calculating-costs) — image token costs confirmed; LOW tile = 85 tokens, HIGH tile adds detail tokens per 512px tile
|
||||
- [OpenAI Structured Output (JSON Schema mode)](https://platform.openai.com/docs/guides/structured-outputs) — `json_schema` mode confirmed as more reliable than `json_object` for typed responses
|
||||
- [Vercel Serverless Function Limits](https://vercel.com/docs/functions/runtimes/node-js#memory-and-compute) — 256MB default, 1024MB on Pro; 60s max execution on Pro
|
||||
- `@cantoo/pdf-lib` confirmed as the import used (not `@pdfme/pdf-lib` or `pdf-lib`) — v1.0 codebase uses this fork throughout
|
||||
|
||||
---
|
||||
|
||||
*Pitfalls research for: Real estate broker web app — custom e-signature, WFRMLS/IDX integration, PDF document signing*
|
||||
*Researched: 2026-03-19*
|
||||
*Pitfalls research for: Teressa Copeland Homes — v1.1 AI field placement, expanded field types, agent signing, filled preview*
|
||||
*Researched: 2026-03-21*
|
||||
|
||||
Reference in New Issue
Block a user