Source-Document Branding Detection (issue #1762)
When a user uploads a source PDF or image, extraction now runs a second,
cheap vision pass over page 1 that looks for a header band and a
footer band (org name, document title, confidentiality notice, logo
graphic). The result is stored on org_charts.suggested_branding (JSONB,
migration 224) as a suggestion only β it is never applied automatically,
mirroring the guardrail that extracted colors are never applied raw.
Flow
POST /api/orgcharts/:id/extractβ after the PDF page renders exist,detectBrandingSuggestion()(services/org-chart/branding-detector.ts) runs concurrently with the main dual-model extraction on page 1 only. Best-effort: any failure logs a warning and extraction proceeds normally.- The detection prompt returns literal band text plus, when an actual logo graphic is present, a fractional bounding box and model-written alt text.
- WCAG 1.4.5 rule: detected text is always suggested as text
(
headerText/footerText), re-rendered in the chartβs AA-safe palette. Text is never cropped into an image. Only a logo graphic is cropped from the page-1 render (in-browser canvas, same technique aspdf-converter.ts), downscaled to the 2000Γ400 branding limits, and staged in R2 atusers/{userId}/branding/{chartId}/suggested-{header|footer}.png. - The branding page (
apps/org-chart/src/app/branding/page.tsx) shows a βWe found branding in your documentβ banner with Apply / Dismiss:POST /api/orgcharts/:id/branding/apply-suggestionβ writes the chart-level branding columns (header_text,header_image_r2_key+ required alt, footer equivalents), copies staged crops to the real branding keys, clearssuggested_branding, deletes staged objects.DELETE /api/orgcharts/:id/branding/suggestionβ dismisses: clears the column and deletes staged objects; real branding untouched.
Guardrails
- Never auto-applied; the seed color remains the branding source of truth.
- Logo boxes are sanitized: clamped to the page, slivers and boxes covering
25% of the page (the chart itself, misreported) are dropped.
- An applied image always carries alt text (model-written, or the
DEFAULT_LOGO_ALTfallback) β the existing required-alt rule holds. - Detection cost is recorded to
cost_ledgerunderorg-chart-extractionwithmetadata.phase = 'branding-detection'(no new operation type). - Re-extraction overwrites the suggestion; staged keys are fixed per chart.
Deploy notes
- Migration
20260726000000_224_org_chart_suggested_branding.sql(additive) must be applied to prod Supabase before deploying the reading code. - API changes ship to BOTH runtimes: Lambda (
deploy:lambda) and the Node server (npm run rebuildon 10.1.1.4).