Commit graph

25 commits

Author SHA1 Message Date
Vadym Samoilenko
dafef834d2 Fix CP14 heading detection via RoleMap + add manual pass support
- enterprise_pdf_checker.py: resolve custom tag names through PDF RoleMap
  in _check_headings so PDFs using /Heading1-style tags (mapped to /H1)
  are correctly detected; add depth guard to walk_tree
- js/results.js: add CP14 (Heading Structure) to CP_TO_CHECK; relax
  H-type restriction so M-type CPs with a linked check also get
  Mark as Passed / Undo buttons
- api.php: add 'Heading Structure' => ['14'] to $check_to_cp for
  server-side recalculate score with heading override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 13:37:19 +00:00
Vadym Samoilenko
2a8db06f0d Fix: load adjusted score when reopening document from history
handleResult() now overlays accessibility_score/wcag_compliance from
.adjusted.json (if it exists) while keeping the original severity_counts
as the recalculation baseline — prevents double-subtraction.

displayResults() auto-calls applyScoreRecalc() on load when the result
was previously adjusted, restoring the (Adjusted) label and WCAG badges
without triggering another server save.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 11:04:07 +00:00
Vadym Samoilenko
7bcf31622f Fix: persist adjusted score to server when Recalculate Score is clicked
recalculateScore() was only updating the DOM — it never called
save_adjusted_result, so .adjusted.json was never written and the
library always showed the original score. Now saves automatically
after each recalculate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 11:00:16 +00:00
Vadym Samoilenko
a5784feda6 Address client feedback: WCAG badges, table grouping, retention, history UX, AI prompt ethics
- Issue 1: Recompute WCAG A/AA compliance badges after dismissing issues (JS +
  backend); exported reports now reflect updated pass/fail status
- Issue 2: Group document-wide table issues into collapsible cards with
  Dismiss All button; reduces noise for multi-table documents
- Issue 3: Split cleanup retention — uploads deleted after 24h, result/meta
  JSONs retained 30 days (RESULTS_RETENTION_HOURS env var, default 720h)
- Issue 4A: Library shows adjusted score when available (.adjusted.json preferred)
- Issue 4B: History page groups documents by retention countdown (red/yellow/green
  sections); adds 30-day retention banner
- Issue 5+6: AI prompt updated — describe people by role/action not appearance,
  use specific brand names; flags images with people for human review

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 10:51:10 +00:00
Vadym Samoilenko
0566f011f8 Fix dark mode on history.html: remove overriding toggleDarkMode/loadTheme
app-history.js was redefining toggleDarkMode() and loadTheme() with a
broken implementation (body.classList + wrong localStorage key 'darkMode')
that overrode the correct versions in utils.js. Removed duplicates so
utils.js handles both pages consistently via data-theme on :root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:31:40 +00:00
Vadym Samoilenko
79aaf050bf PDF report reflects adjusted score + manual pass for Matterhorn H-type CPs
- api.php: add save_adjusted_result action that merges dismissed issues,
  check overrides and recalculated score into {job_id}.adjusted.json;
  handleExport() now prefers .adjusted.json over .result.json
- js/results.js: displayMatterhorn() shows Mark as Passed / Undo buttons
  for H-type CPs (CP04, CP13) linked to overridden checks; overrideCheck /
  unoverrideCheck refresh Matterhorn table and recompute overall banner
- js/batch.js: exportReport() saves adjusted result before opening export
  URL, using pre-opened window to avoid popup blockers
- report_generator.py: filter dismissed issues, show (Adjusted) badge,
  Manual Pass in checks and Matterhorn tables; switch generate_html() to
  Montserrat + Oliver branding (#1a1a1a header, #FFC407 skip-link)
- css/styles.css: fix dark-mode log-header from blue-ish #252840 to #242424

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 16:28:53 +00:00
Vadym Samoilenko
50cf941f14 Fix openHistoryJob: unwrap data.data from API response 2026-03-13 15:23:55 +00:00
Vadym Samoilenko
62094f4dfa Move document history to separate history.html page
- history.html: standalone page with My Documents table + auth
- js/history.js: renderHistory, loadHistory, deleteHistoryJob logic
- js/app-history.js: MSAL auth init for history.html
- index.html: remove history section, add 'My Documents' link in header
- js/app.js: show historyLink after auth, open job from ?job_id= URL param
- deploy.sh: include history.html in deploy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:21:19 +00:00
Vadym Samoilenko
373bf88a29 Fix history: read jobs from data.data.jobs (API wraps in data key) 2026-03-13 15:18:20 +00:00
Vadym Samoilenko
2105a0052e Add console debug logging to loadHistory 2026-03-13 15:17:24 +00:00
Vadym Samoilenko
1126c8a700 Fix history: correct score field name + add delete button
- api.php: read accessibility_score (not score) from result.json
- api.php: handleDelete() also removes .dismissed.json, .overrides.json, .error.log
- js/app.js: add Delete button to each history row with confirm dialog
- css/styles.css: red hover style for delete button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:13:30 +00:00
Vadym Samoilenko
9a9712e852 Fix MSAL CDN URL: switch from alcdn.msauth.net (404) to jsDelivr
alcdn.msauth.net/browser/2.38.3/js/msal-browser.min.js returns 404.
Using cdn.jsdelivr.net (npm mirror) with @azure/msal-browser@2 instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 15:07:41 +00:00
Vadym Samoilenko
e60639c58d Add SSO user isolation and document history dashboard
- api.php: extractUserFromToken() decodes Azure AD JWT payload (oid/name/email)
- Upload: stores user_id, user_name, user_email in job .meta.json
- handleList(): filters jobs by authenticated user's oid — full user isolation
  (jobs without user_id are excluded for authenticated users to prevent leakage);
  enriches each entry with score, grade, critical/error counts from result JSON
- index.html: "My Documents" history section, shown after login
- js/app.js: showAuthenticatedUI() triggers loadHistory(); full renderHistory()
  renders sortable table with score, grade, severity badges, and Open/HTML/PDF/JSON
  action buttons; openHistoryJob() loads any past result into the results panel
- js/results.js: calls loadHistory() after displayResults() so table refreshes
  immediately after a new check completes
- css/styles.css: history table styles with colour-coded score/grade/severity badges

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:58:18 +00:00
Vadym Samoilenko
148853c699 Add WCAG compliance summary, level badges, font names, next steps
enterprise_pdf_checker.py:
- WCAG_LEVELS dict maps all 2.1 criteria to A/AA/AAA
- AccessibilityIssue.to_dict() now includes wcag_level field
- _check_fonts() collects actual font names into details dict
  instead of just counting (details.non_embedded_fonts list)
- _generate_summary() adds wcag_compliance block:
    level_a / level_aa bool + failing criteria lists
- _generate_summary() adds next_steps: top 8 prioritised actions
  (Critical → Error → Warning, deduplicated by recommendation text)

js/results.js:
- displayWcagCompliance(): renders pass/fail badges for Level A/AA
- displayNextSteps(): numbered action list with priority badges
- createIssueCard(): shows wcag_level pill (A/AA/AAA) alongside
  WCAG criterion link

index.html:
- #wcagCompliance div between statsGrid and scoreBreakdown
- #nextStepsCard below remediationCard

css/styles.css:
- .wcag-badge, .wcag-compliance-row, .wcag-badge-level/status
- .wcag-level-badge + .wcag-level-A/AA/AAA colour variants
- .next-step-item, .next-step-num, .next-step-body/action/meta

report_generator.py:
- HTML report: WCAG conformance section + next steps table
  between score card and issues table
- PDF report: compliance banners + next steps table in sections_html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:32:21 +00:00
Vadym Samoilenko
304526a8c4 Fix 13 WCAG accessibility violations in the checker UI itself
HTML:
- Move <div id="msalConfig"> out of <head> (invalid HTML)
- Add skip-to-main-content link (WCAG 2.4.1)
- Wrap content in <main id="main-content">
- Auth overlay: aria-modal, aria-describedby, aria-describedby on p
- Microsoft SVG: aria-hidden="true" (decorative)
- Tab buttons: aria-controls; panels: role=tabpanel, aria-labelledby
- Score number: <div> → <output> element
- Marker legend: role=legend (invalid) → role=region + aria-label
- Reset zoom button: aria-label added

CSS:
- input:focus outline:none → outline:2px solid accent (WCAG 2.4.7)
- --text-muted #696969 → #5a5a5a (~5.5:1 contrast, was 4.35:1)
- Skip link styles (visible on focus)
- @media (prefers-reduced-motion: reduce) disables all animations

JS:
- upload.js/batch.js: keydown Enter/Space activates upload areas (WCAG 2.1.1)
- results.js: issue cards get role=listitem inside role=list
- results.js: filterIssues() updates aria-pressed on all filter buttons
- results.js: displayResults() focuses resultsSection for screen readers
- utils.js: aria-valuenow set on role=progressbar element, not fill div

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:19:20 +00:00
Vadym Samoilenko
c932e8b7e1 Make WCAG criterion badges clickable links to Understanding pages
Each issue card's WCAG criterion (e.g. "1.4.3") is now a link to the
WAI Understanding page at w3.org. Comma-separated multi-criteria and
PDF/UA are handled separately. Links open in a new tab.

- js/utils.js: WCAG_SLUGS map + wcagCriterionLinks() helper
- js/results.js: issue-meta now calls wcagCriterionLinks()
- css/styles.css: .wcag-link style (dotted underline, hover accent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:05:55 +00:00
Vadym Samoilenko
8b70da3584 Add "Mark as Passed" check overrides and "Recalculate Score" feature
- api.php: override_check / unoverride_check endpoints write per-job
  .overrides.json; handleResult() injects overridden_checks on reload
- js/results.js: score breakdown rows show "Mark as Passed" / "Undo"
  buttons; recalculateScore() adjusts penalty for dismissed issues and
  base score for manual overrides without mutating original data
- index.html: score display gains hidden (Adjusted) badge and
  Recalculate Score button, revealed after first check
- css/styles.css: btn-mark-passed, btn-unoverride, check-manual-pass,
  btn-recheck, score-adjusted-label styles
- js/utils.js: escapeAttr() helper for safe inline onclick values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 14:01:52 +00:00
Vadym Samoilenko
a5cd1af982 Fix color contrast false positives; table caption INFO; dismiss button more visible
Color contrast:
- Sample pixels 8px apart vertically instead of adjacent horizontal pixels
- Filter out near-uniform pairs (|Δlum| < 0.08) — eliminates photo/gradient noise
- ERROR threshold: >60% of significant edges fail (was 15% of all pixels)
- WARNING threshold: >30% (was 5%)
- Returns early with 'image-only page' if <20 significant edges found

Tables:
- Caption warning downgraded WARNING → INFO (table may have visible title nearby)
- Does not count toward check pass/fail anymore

Dismiss button:
- Renamed 'Dismiss' → '✕ False Positive' (clearer intent)
- Added background color so it's visible against card
- font-size 11→12px, padding increased

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 19:15:28 +00:00
Vadym Samoilenko
e0f961ffb9 Fix pin-click navigation, cap image quality noise, drop Google Vision label spam
Page viewer:
- loadVisualPage() now accepts highlightNum; highlights marker after image onload
  (was using fixed 300ms timeout which fired before GCS image finished loading)
- viewOnPage() passes markerNum directly to loadVisualPage()

Image analysis:
- Quality concerns downgraded WARNING → INFO (advisory, not WCAG violations)
- Cap at 2 concerns per image (was unlimited)
- Google Vision label detections removed — not actionable accessibility issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:56:41 +00:00
Vadym Samoilenko
5652b67a07 Fix progress bar stuck at 30% during Cloud Run synchronous processing
Cloud Run processes PDFs synchronously (2-5 min). The await startCheck()
call blocks JS, so progress never advanced past 30%. Add a setInterval
timer before the await that advances through realistic stages every 18s,
covering the full processing window. Timer is cleared on completion/error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:19:46 +00:00
Vadym Samoilenko
c4ffb94351 Merge Cloud Run migration; resolve handleResult() conflict
Keep dismissed_indices injection in handleResult() from our QA
fixes alongside the Cloud Run rewrite from origin/master.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:08:04 +00:00
Vadym Samoilenko
ac8aedf4a3 Implement QA report fixes: scoring, Matterhorn, dismiss, PDF report, UX
Part 1 — CSS/Contrast/Accessibility:
- Raise --text-muted contrast to WCAG AA (#696969 light, #9a9a9a dark)
- Add body font-size: 16px baseline
- Enlarge #themeToggle to 15px / 10px 20px padding

Part 2 — Start Button (user-controlled analysis):
- Upload no longer auto-starts check; shows ready state with filename/size
- New showReadyState() / removeFile() functions in upload.js
- beginCheck() now shows progress + hides ready state on click
- Add prominent "Check Another PDF" button at bottom of results

Part 3 — Scoring recalibration:
- Replace deduction formula with check-pass ratio + soft penalty (cap 20)
- Fix run_check() to only examine issues added by the current check
- Add score_breakdown (per-check table) to JSON output + results UI
- Downgrade readability ERROR → WARNING (advisory, not hard failure)

Part 4 — Auto-fix debugging:
- Remediation failure now returns up to 2000 chars of log (was 500)
- pdf_remediation.py: stderr output, sys.exit(0/1), output dir creation

Part 5 — Error location: View on Page button on each issue card

Part 6 — Matterhorn Protocol PDF/UA-1:
- _build_matterhorn_summary() maps 19 checks → 31 checkpoints
- Matterhorn card in index.html with grouped PASS/FAIL/Not-tested table
- Correct M/H badges per checkpoint

Part 7 — Dismiss / False Positive:
- dismissed_issues table in db/init.sql + dismiss/undismiss in db_manager.py
- api.php: dismiss/undismiss endpoints (file-backed), dismissed_indices
  injected into both handleStatus and handleResult responses
- results.js: dismissIssue/undismissIssue with visual strikethrough
- CSS: .dismissed, .btn-dismiss, .btn-undismiss styles

Part 8 — PDF Report (WeasyPrint):
- generate_pdf() in report_generator.py: PAC-style A4, Oliver branding
- api.php handleExport() supports format=pdf
- index.html: "PDF Report" download button in results header
- requirements.txt: weasyprint>=60.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 18:06:32 +00:00
michael
4080638856 Migrate PDF processing from Redis worker to Google Cloud Run
Replace the Redis queue + Python worker daemon with a synchronous HTTP
call to a Cloud Run service, eliminating Redis and simplifying the
infrastructure from 4 containers (web, worker, redis, postgres) to just
web + postgres (with Cloud Run handling processing).

- Add cloudrun_service.py: Flask app wrapping EnterprisePDFChecker with
  POST /check and GET /health endpoints, GCS image upload
- Add Dockerfile.cloudrun + requirements-cloudrun.txt for Cloud Run image
- Add cloudbuild.yaml for Cloud Build with custom Dockerfile
- Rewrite api.php: remove all Redis code, add Cloud Run OIDC auth
  (getCloudRunToken), synchronous processing in handleCheck(), file-based
  rate limiting, GCS redirect in handleImage(), DB helper updateJobInDatabase()
- Update js/upload.js: handle synchronous completed response from Cloud Run,
  increase poll timeout to 15 minutes
- Update js/page-viewer.js: use GCS URLs directly for page images
- Simplify docker-compose.yml and docker-compose.prod.yml: remove worker
  and redis services
- Remove PHP Redis extension from Dockerfile.web
- Set 900s timeouts across nginx, PHP-FPM, gunicorn, curl, and Cloud Run
- Update cleanup.py: remove result_images pattern (now on GCS), add
  rate_limits cleanup
- Update .env.example: replace Redis vars with Cloud Run/GCS config

Cloud Run service deployed to:
  https://pdf-checker-bcb6ipdqka-uc.a.run.app
GCS bucket: gs://optical-pdf-images (7-day lifecycle, public read)
GCP project: optical-414516

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 14:50:38 -06:00
Vadym Samoilenko
345cc1ceb2 Switch to Oliver branding: Montserrat font, black/#FFC407 palette, fix dark mode contrast
- Font: Outfit/Figtree → Montserrat
- Accent: coral #e8553d → Oliver yellow #FFC407 with black text
- Dark mode: neutral blacks instead of blue-tinted navy
- Fix score display, stat cards, and log entries contrast in dark mode
- Replace hardcoded light-mode colors in JS with CSS variables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:54:47 +00:00
Vadym Samoilenko
112719b2c5 Add Docker stack, frontend redesign, and visual page inspector fix
- Redesigned frontend with Outfit/Figtree typography, coral accent palette,
  noise texture, glassmorphism header, and staggered animations
- Split monolithic index.html into modular JS (app, api, upload, batch,
  results, page-viewer, utils) and extracted CSS
- Fixed worker.py to generate page images for Visual Page Inspector
- Added Docker Compose stack (web, worker, redis, postgres)
- Added batch upload, HTML report export, rate limiting, and Redis queue
- Extended test suite with checker, remediation, worker, and DB tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 18:12:44 +00:00