Commit Graph

51 Commits

Author SHA1 Message Date
Chandler Copeland
9e677f9505 feat(18-01): add documentTemplates table and relation to schema.ts
- Add document_templates pgTable with 7 columns: id, name, formTemplateId, signatureFields, archivedAt, createdAt, updatedAt
- formTemplateId FK references formTemplates.id with no onDelete cascade
- signatureFields typed as SignatureFieldData[] via jsonb, nullable (template starts empty)
- archivedAt nullable timestamp for soft-delete (NULL = active)
- Add documentTemplatesRelations joining to formTemplates
2026-04-06 12:15:01 -06:00
Chandler Copeland
6013dfe89f feat: client-text and client-checkbox field types — signer fills text/checks boxes on signing page 2026-04-06 11:35:30 -06:00
Chandler Copeland
2d2a43a3c9 fix(docker): polyfill DOMMatrix/ImageData/Path2D for pdfjs-dist in linux/amd64 container via NODE_OPTIONS --require 2026-04-03 18:02:39 -06:00
Chandler Copeland
81ce0b9ab0 feat(clients): multi-contact support — co-buyers, auto-seed document signers from client contacts 2026-04-03 17:37:39 -06:00
Chandler Copeland
07cfaf0511 fix(docker): add credentials fields to auth provider, postgres service, expose port 5433, AUTH_URL/AUTH_TRUST_HOST in env example 2026-04-03 17:21:01 -06:00
Chandler Copeland
fa7d6a9636 feat(17-01): enable standalone output, limit DB pool to 5, remove @vercel/blob
- Add output: 'standalone' to next.config.ts for Docker three-stage build
- Change postgres(url) to postgres(url, { max: 5 }) to avoid Neon free tier exhaustion
- Remove dead @vercel/blob dependency (imported nowhere in codebase)
2026-04-03 16:53:18 -06:00
Chandler Copeland
e1cdfe9b7b feat(15-01): add sendSignerCompletionEmail to signing-mailer
- Sends plain-text email with signed document download link to signer
- Follows established createTransporter() + sendMail() pattern
- Subject: 'Signed copy ready: {documentName}', 72h expiry in body text
2026-04-03 15:43:17 -06:00
Chandler Copeland
70c48cc377 feat(15-01): extend createSigningToken with signerEmail, add signer-download token pair
- createSigningToken now accepts optional signerEmail param and persists to DB
- Added createSignerDownloadToken (72h TTL, purpose: signer-download)
- Added verifySignerDownloadToken with purpose claim validation
2026-04-03 15:43:00 -06:00
Chandler Copeland
c658f13ea0 feat(14-01): add multi-signer types and columns to schema.ts
- Add signerEmail?: string to SignatureFieldData interface
- Add getSignerEmail() helper function with fallback pattern
- Add DocumentSigner interface { email, color }
- Add documents.signers JSONB column typed as DocumentSigner[]
- Add documents.completionTriggeredAt nullable TIMESTAMP column
- Add signingTokens.signerEmail nullable TEXT column
2026-04-03 15:15:32 -06:00
Chandler Copeland
6265a64a50 chore(13-04): remove debug console.log from classifyFieldsWithAI
- Remove 3 console.log statements that printed blank count, all blank descriptions, and AI classifications
- These were development debug statements; not appropriate for production code
- Tests pass (prepare-document.test.ts: 10/10), TypeScript clean
2026-04-03 14:31:15 -06:00
Chandler Copeland
c80133ea58 fix(13): stamp page numbers on rendered images, fix signature block pattern in prompt
- Red PAGE N label stamped on each image so GPT-4o correctly attributes fields to pages
- Prompt: add 'blank line above label' signature block pattern (common in real estate docs)
- Prompt: explicit rule — place field on blank underline, not on the (Label) text below it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:52:31 -06:00
Chandler Copeland
8ac5acb486 fix(13): reduce field heights, nudge y-offset, tighten height prompt guidance
- Max heights reduced: text/date 16pt, initials 18pt, signature 26pt
- 0.5% y nudge pushes fields onto the underline instead of floating above it
- Prompt specifies 1.2% height for text/date, 1.8% for signatures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:49:11 -06:00
Chandler Copeland
48788dea23 fix(13): use AI-estimated field sizes with type bounds, stricter no-inline-text rule
- Replace fixed 144x36 with AI widthPct/heightPct clamped to per-type min/max
  (signatures 100-250x20-40pt, initials 36-80x16-28pt, date 60-130x14-24pt, text 60-280x14-24pt)
- Prompt: explicit 'no inline body text' rule — if text is part of a sentence, skip it
- Prompt: widthPct should match visual underline width, heightPct kept thin (~2-2.5%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:46:04 -06:00
Chandler Copeland
e7bf5abb9f fix(13): switch to GPT-4o vision — render PDF pages as images for accurate field placement
- extractPdfText now renders each page to JPEG via @napi-rs/canvas + pdfjs-dist (108dpi)
- field-placement.ts sends rendered page images to GPT-4o with vision (detail: high)
- AI can now visually identify underlines, signature blocks, date fields, initials boxes
- System prompt focuses on visual cues (blank lines, boxes) not text pattern matching
- Handles multi-field lines: separate fields for signature blank and date blank on same line

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:40:47 -06:00
Chandler Copeland
b5216a8542 fix(13): extract text with line positions for accurate AI field placement
- extractPdfText now returns TextLine[] with yPct/xPct per line instead of flat text blob
- AI can now see spatial layout (where blank lines/underscores actually are vs body text)
- Rewrote system prompt: explicit rules about blank lines/underscores/signature blocks,
  place ALL blanks even without prefill value, match field type to label pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:35:02 -06:00
Chandler Copeland
c67d56dc48 fix(13-01): upgrade to gpt-4o, remove checkboxes, clamp AI coords to page bounds
- gpt-4o-mini replaced with gpt-4o for better placement accuracy
- checkbox removed from schema enum and filtered in loop (positions are input-dependent)
- y coordinate clamped to [0, pageHeight - fieldHeight] to prevent fields rendering
  outside the PDF canvas when AI returns yPct near 100%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:29:49 -06:00
Chandler Copeland
3f4ca5a8e5 fix(13-01): use file:// worker path for pdfjs-dist 5.x fake-worker in Node.js
Empty string workerSrc is falsy — PDFWorker.workerSrc getter throws before
_setupFakeWorkerGlobal can dynamically import the worker file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 17:19:29 -06:00
Chandler Copeland
c1e1e5ec49 feat(13-01): implement aiCoordsToPagePdfSpace and AI field utilities
- Install openai 6.32.0 (npm package, listed in dependencies)
- Create src/lib/ai/extract-text.ts — pdfjs-dist legacy build server-side text extraction
  - extractPdfText(filePath) returning PageText[] with page, text, width, height
  - GlobalWorkerOptions.workerSrc = '' for Node.js fake-worker mode
  - Text per page capped at 2000 chars for GPT-4o-mini context limit
- Create src/lib/ai/field-placement.ts — GPT-4o-mini structured output + coord conversion
  - aiCoordsToPagePdfSpace() converts AI top-left pct coords to PDF bottom-left points
  - classifyFieldsWithAI() uses manual json_schema (NOT zodResponseFormat — broken with Zod v4)
  - Standard field sizes: checkbox=24x24pt, others=144x36pt
  - textFillData keyed by field UUID (not label) per Phase 12.1 design
- All 3 unit tests pass (GREEN phase confirmed)
2026-03-21 17:00:34 -06:00
Chandler Copeland
f7d74c0523 test(13-01): add failing aiCoordsToPagePdfSpace unit tests
- Add ai-coords.test.ts with 3 test cases covering US Letter (612x792 pts)
- Tests import from lib/ai/field-placement which does not exist yet (RED phase)
- Cases: text field near top, checkbox near bottom-right, client-sig at center
2026-03-21 16:59:11 -06:00
Chandler Copeland
df02a1e3f7 feat(12.1-01): replace positional text fill with field-ID-keyed lookup
- Remove AcroForm Strategy A (getForm/flatten) — no longer needed
- Remove positional sorting loop (textFields_sorted, remainingEntries, fieldConsumedKeys)
- Remove Strategy B top-of-page UUID stamp (unstampedEntries)
- Add Phase 12.1 field-ID-keyed loop: textFields[field.id] direct lookup
- Update JSDoc to document new keying strategy
2026-03-21 16:20:25 -06:00
Chandler Copeland
bce2a980d2 fix(12-02): draw text fill values at placed text field box coordinates
- Bug 3: text type fields previously drew nothing at field coordinates;
  textFillData values were only stamped at top of page 1 via Strategy B,
  making them invisible to the agent inspecting the placed boxes
- Sort placed text fields by page asc / y desc (reading order) and assign
  textFillData entries sequentially to each field box position
- Text drawn at field.x+4, field.y+4 with font size capped 6–11pt to fit
  within the field height; dark near-black color for legibility
- fieldConsumedKeys set tracks which entries were rendered at field coords;
  Strategy B only stamps remaining entries not consumed by a field box
- TypeScript compiles clean; zero errors
2026-03-21 15:50:30 -06:00
Chandler Copeland
fae1cf1b68 feat(11.1-02): add agentInitialsData param to preparePdf and embed at agent-initials fields
- Add agentInitialsData as 6th optional param (default null)
- Embed agent initials PNG once before field loop (embed-once-draw-many pattern)
- Add agent-initials branch in field loop: drawImage at field coordinates
- Existing initials (client-initials) and agent-signature branches untouched
2026-03-21 15:02:51 -06:00
Chandler Copeland
33f499c61b feat(11.1-01): DB migration, API routes, schema type updates for agent initials storage
- Add agentInitialsData text column to users table (drizzle/0009_luxuriant_catseye.sql)
- Add 'agent-initials' to SignatureFieldType union in schema.ts
- Update isClientVisibleField() to exclude both agent-signature and agent-initials
- Create GET/PUT /api/agent/initials route with auth guard and 50KB size limit
2026-03-21 14:58:39 -06:00
Chandler Copeland
d9652e1f87 feat(11-02): preparePdf() gains agentSignatureData param and embeds at agent-sig fields
- Add optional agentSignatureData: string | null = null as 5th parameter
- Import PDFImage from @cantoo/pdf-lib for typed agentSigImage variable
- Embed PNG once before field loop, store as agentSigImage
- Replace agent-signature stub with drawImage at field.x/y/width/height
2026-03-21 14:06:12 -06:00
Chandler Copeland
e07ed306cd feat(11-01): DB migration and API routes for agent signature storage
- Add agentSignatureData TEXT column to users table in schema.ts
- Generate migration 0008_windy_cloak.sql (ALTER TABLE users ADD COLUMN agent_signature_data text)
- Apply migration to local postgres database
- Create GET/PUT /api/agent/signature route with auth guard and input validation
2026-03-21 14:02:01 -06:00
Chandler Copeland
e179b9284f fix(10-03): transparent field boxes and fixed-size checkbox in FieldPlacer
- preparePdf: remove opaque fill from all field type rectangles (signature,
  initials, checkbox, date, text) — underlying PDF content now shows through
- preparePdf: checkbox draws X lines only (no border rectangle); date draws
  no placeholder at all; text draws nothing (position marker only)
- sign route: remove white overwrite rectangle on date stamp — date text
  draws directly on existing PDF content
- FieldPlacer: suppress resize corner handles for checkbox fields; hide
  "Checkbox" label (too small at 24x24pt); checkbox is fixed-size only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-21 13:41:04 -06:00
Chandler Copeland
7510c8ee08 feat(10-02): type-branched field rendering in preparePdf()
- Add getFieldType import from @/lib/db/schema
- Replace single-variant blue loop with branched rendering per field type
- client-signature: unchanged blue rectangle + "Sign Here"
- initials: purple rectangle + "Initials" label
- checkbox: gray rectangle + X diagonal lines (embedded at prepare time)
- date: amber rectangle + "Date" label (actual date stamped at POST time)
- text: light gray rectangle, no label (visual marker only)
- agent-signature: skipped (no placeholder drawn)
2026-03-21 12:49:20 -06:00
Chandler Copeland
baa1c785a5 feat(09-01): add property_address column to clients — schema, migration, server actions
- Add propertyAddress: text("property_address") nullable column to clients pgTable
- Generate migration drizzle/0007_equal_nekra.sql: ALTER TABLE "clients" ADD COLUMN "property_address" text
- Apply migration successfully to local postgres database
- Extend clientSchema Zod schema with propertyAddress: z.string().optional()
- createClient: persist propertyAddress || null to coerce empty string to NULL
- updateClient: persist propertyAddress || null alongside name, email, updatedAt
2026-03-21 12:13:55 -06:00
Chandler Copeland
2dd1b6101f feat(08-01): extend SignatureFieldData with type discriminant and helper exports
- Add SignatureFieldType union type with 6 literals (client-signature, initials, text, checkbox, date, agent-signature)
- Add optional type field to SignatureFieldData interface (backward-compat; v1.0 docs have no type)
- Export getFieldType() helper that coalesces field.type ?? 'client-signature'
- Export isClientVisibleField() predicate that returns false for agent-signature only
2026-03-21 11:46:49 -06:00
Chandler Copeland
cd4cb75b60 feat(07-01): add createAgentDownloadToken and verifyAgentDownloadToken
- Appends two new exports to token.ts (existing exports untouched)
- purpose: 'agent-download', 5-min TTL, no DB record
- Mirrors existing createDownloadToken/verifyDownloadToken pattern
2026-03-21 10:33:53 -06:00
Chandler Copeland
7121279654 feat(06-06): update domain to tcopelandhomes.com, configure Resend SMTP 2026-03-21 09:42:10 -06:00
Chandler Copeland
a276da0da1 feat(06-05): download token utilities + download API route
- Add createDownloadToken and verifyDownloadToken to token.ts (15-min TTL, purpose:'download' claim)
- Create GET /api/sign/[token]/download route: validates dt query param JWT, streams signedFilePath as PDF
- Path traversal guard: signedFilePath must start with UPLOADS_DIR
- Auto-fix: Buffer cast to Uint8Array for Response BodyInit compatibility (Next.js 16 / TypeScript strict)
2026-03-20 11:41:18 -06:00
Chandler Copeland
f41db49ff7 feat(06-02): branded signing request email + mailer utilities
- Add SigningRequestEmail.tsx React Email component (navy/gold brand colors, CTA button)
- Add signing-mailer.tsx with sendSigningRequestEmail() and sendAgentNotificationEmail()
- Uses CONTACT_SMTP_* env vars (same SMTP provider as contact form)
- Sender: "Teressa Copeland" <teressa@teressacopelandhomes.com>
2026-03-20 11:29:05 -06:00
Chandler Copeland
2929581ab9 feat(06-01): create signing utility library (token, audit, embed)
- token.ts: createSigningToken() + verifySigningToken() using jose HS256
- audit.ts: logAuditEvent() inserts typed audit events with server timestamp
- embed-signature.ts: embedSignatureInPdf() embeds PNG sigs, returns SHA-256 hash
- added SIGNING_JWT_SECRET to .env.local (random 32-char base64 secret)
2026-03-20 11:24:56 -06:00
Chandler Copeland
fa68a1bcb4 feat(06-01): install packages + extend schema + generate migration
- installed signature_pad, @react-email/render, @react-email/components
- added signingTokens table (jti pk, documentId, expiresAt, usedAt)
- added auditEvents table with auditEventTypeEnum (6 event types)
- added signedFilePath, pdfHash, signedAt columns to documents table
- generated and applied migration 0005_signing_flow.sql
2026-03-20 11:24:02 -06:00
Chandler Copeland
c4e8d01784 fix(05-04): move text stamp to 60pt from top, increase font size to 10pt
- Change startY from pageHeight-20 to pageHeight-60 (~0.83 inch from top)
- Increase lineHeight from 12 to 14 for better readability
- Increase stamp font size from 8pt to 10pt for better visibility
2026-03-20 00:42:03 -06:00
Chandler Copeland
ef10dd5089 fix(05-04): always stamp text fill data into prepared PDF
Strategy A still attempts AcroForm filling by field name (matching
fields in the PDF's form dict). Strategy B is now a mandatory fallback:
any text field entries that did not match an AcroForm field (or when
the PDF has no AcroForm at all) are drawn as 'key: value' text lines
near the top of page 1 using @cantoo/pdf-lib drawText.

This ensures text fill data supplied in the PreparePanel is always
visible in the output PDF regardless of whether the source PDF was
built with interactive form fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:22:17 -06:00
Chandler Copeland
05915aa562 fix(05-04): pre-select document client and add manual email entry
- PreparePanel now receives separate assignedClientId (nullable) and
  defaultClientId props so it can distinguish an explicitly locked
  client from just a default
- When document already has assignedClientId: show locked read-only
  display; user cannot change the primary recipient
- When document has no assignedClientId: default to document owner in
  dropdown but allow changing; option to clear and enter email manually
- Added textarea for additional/CC email addresses (comma or newline
  separated) that is always visible for either mode
- POST /api/documents/[id]/prepare now accepts and stores emailAddresses
  array alongside assignedClientId
- Added email_addresses jsonb column to documents table via migration
  0004_military_maximus.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 00:21:34 -06:00
Chandler Copeland
34ed0baa43 test(05-01): add unit tests for Y-flip coordinate conversion formula
- Created src/lib/pdf/__tests__/prepare-document.test.ts with 10 test cases
- Tests verify Y-axis flip: screenY=0 → pdfY≈792, screenY=792 → pdfY≈0
- Tests verify scale-invariance at both 1:1 and 50% zoom ratios
- Installed jest, ts-jest, @types/jest and added jest config to package.json
- All 10 tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:55:27 -06:00
Chandler Copeland
c81e8ea838 feat(05-01): add preparePdf utility and fields/prepare API routes
- Installed @cantoo/pdf-lib for server-side PDF mutation
- Created src/lib/pdf/prepare-document.ts with preparePdf function using atomic tmp->rename write pattern
- form.flatten() called before drawing signature rectangles
- Created GET/PUT /api/documents/[id]/fields routes for signature field storage
- Created POST /api/documents/[id]/prepare route that calls preparePdf and transitions status to Sent
- Fixed pre-existing null check error in scripts/debug-inspect2.ts (Rule 3: blocking build)
- Build compiles successfully with 2 new API routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:54:41 -06:00
Chandler Copeland
d67130da20 feat(05-01): extend documents schema with 4 new columns + migration 0003
- Added SignatureFieldData interface export to schema.ts
- Added signatureFields (jsonb), textFillData (jsonb), assignedClientId (text), preparedFilePath (text) nullable columns to documents table
- Added jsonb import to drizzle-orm/pg-core imports
- Generated and applied migration 0003_cool_natasha_romanoff.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 23:53:12 -06:00
Chandler Copeland
c1f60cadf6 feat(04-03): add AddDocumentModal, PdfViewer, and document detail page
- Create AddDocumentModal: searchable forms library list + custom file picker
- Wire Add Document button into ClientProfileClient in Documents section header
- Update DocumentsTable: document names now link to /portal/documents/{id}
- Create PdfViewer with page nav, zoom, and download controls (pdfjs worker via import.meta.url)
- Create /portal/documents/[docId] page: server component with auth check, doc/client query
- Add documentsRelations and clientsRelations to schema.ts for db.query with-relations support
- Build verified: /portal/documents/[docId] route present, no errors
2026-03-19 21:44:17 -06:00
Chandler Copeland
bbbbdbed5e feat(04-01): add formTemplates table and extend documents schema
- Add formTemplates table (id text PK, name, filename unique, createdAt, updatedAt)
- Add formTemplateId (nullable FK) and filePath (nullable text) to documents table
- Generate and apply migration 0002_wealthy_zzzax.sql
- Create seeds/forms/.gitkeep to track seed directory in git
2026-03-19 21:32:30 -06:00
Chandler Copeland
5b87201b28 feat(03-02): add createClient, updateClient, deleteClient server actions
- 'use server' file with Zod validation (name min 1 char, valid email)
- createClient: validate, insert, revalidatePath /portal/clients
- updateClient: bind pattern (id, prevState, formData), revalidates client list + profile
- deleteClient: auth check, delete by id, revalidatePath /portal/clients
- Fixed Zod v4 .issues access (not .errors — v4 API change)
2026-03-19 16:38:49 -06:00
Chandler Copeland
00f9c7c9f0 feat(03-01): protect /portal routes and update post-login redirect
- middleware.ts: add /portal/:path* to matcher array
- auth.config.ts: add isPortalRoute check, redirect unauthenticated to /agent/login
- auth.config.ts: change post-login redirect from /agent/dashboard to /portal/dashboard
- agent dashboard page: replace stub with redirect to /portal/dashboard
2026-03-19 16:17:59 -06:00
Chandler Copeland
f8f8b8f4ba feat(03-01): add clients and documents tables to Drizzle schema
- Add pgEnum import and documentStatusEnum (Draft, Sent, Viewed, Signed)
- Add clients table (id, name, email, createdAt, updatedAt)
- Add documents table (id, name, clientId FK, status enum, sentAt, createdAt)
- Generate migration 0001_watery_blindfold.sql and apply to local PostgreSQL
2026-03-19 16:17:26 -06:00
Chandler Copeland
39f233dbb4 feat(02-02): install nodemailer and create contact mailer + server action
- Add nodemailer@^7.0.7 and @types/nodemailer (v7 required by next-auth peer dep)
- Create src/lib/contact-mailer.ts: Nodemailer SMTP transporter + sendContactEmail()
- Create src/lib/contact-action.ts: Server Action with Zod validation, honeypot check
- SMTP credentials read from CONTACT_EMAIL_USER/PASS/SMTP_HOST/PORT env vars
- Add CONTACT_* placeholder vars to .env.local (gitignored, for local setup docs)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:59:23 -06:00
Chandler Copeland
39af0f19ba fix(auth): resolve middleware Edge Runtime + layout redirect loop
Two bugs:
1. auth.ts imported postgres (Node.js TCP) which crashes in Edge Runtime,
   causing Auth.js to silently fall back to redirecting all requests to login.
   Fix: split into auth.config.ts (Edge-safe, no DB) used by middleware,
   and auth.ts (full, with DB) used by server components.

2. /agent/layout.tsx applied to /agent/login, so unauthenticated login page
   visits redirected to themselves in an infinite loop.
   Fix: moved dashboard + layout into (protected) route group so login page
   has no auth layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:08:04 -06:00
Chandler Copeland
0a75442af3 fix(db): swap @neondatabase/serverless for postgres.js (local dev support)
Neon serverless driver requires remote WebSocket — incompatible with local
PostgreSQL. Replaced with postgres.js (drizzle-orm/postgres-js) in db/index.ts,
scripts/seed.ts, and drizzle.config.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 14:00:32 -06:00
Chandler Copeland
e5db79a8f0 feat(01-01): configure Auth.js v5 JWT + Credentials, route protection middleware
- Created src/lib/auth.ts with NextAuth JWT strategy, 7-day rolling session, Credentials provider
- Created src/app/api/auth/[...nextauth]/route.ts with GET/POST handlers and force-dynamic
- Created middleware.ts at project root (not src/) protecting /agent/* routes
- Fixed db/index.ts: lazy Proxy singleton prevents neon() crash during Next.js build
- npm run build passes; /api/auth/[...nextauth] renders as Dynamic route
2026-03-19 13:33:15 -06:00