Skip to content

feat(web): redesign the wizard UI on the Metzler design system with full UI i18n - #163

Open
Liohtml wants to merge 1 commit into
mainfrom
claude/ui-ux-orchestration-l5ja6l
Open

feat(web): redesign the wizard UI on the Metzler design system with full UI i18n#163
Liohtml wants to merge 1 commit into
mainfrom
claude/ui-ux-orchestration-l5ja6l

Conversation

@Liohtml

@Liohtml Liohtml commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Complete UI/UX overhaul of the browser wizard:

  • Rebuild index.html on the Metzler design system: design tokens (teal
    palette, graphite text scale, spacing/radius/shadow tokens), 100rem
    container, PDP stepper instead of ad-hoc tabs, floating-label form
    inputs, kit cards/alerts/buttons/badges/file-download components,
    teal-700 footer, and inline stroke icons throughout. All measurements
    in rem, all colors from tokens.
  • Localize the entire UI via the existing i18n catalogs: ~70 new ui_*
    keys in en/de/fr/es plus a header language switcher (?lang=, validated
    server-side, falls back to MEDCHECK_LANGUAGE).
  • Replace the fake progress simulation with an honest submit flow:
    POST /api/analyze as JSON and surface the real server response
    (501 preview -> warning alert, other errors -> danger alert), with
    text inserted via textContent only.
  • Add an explicit cloud-LLM consent checkbox (allow_cloud_llm) that is
    required for cloud models and hidden for the local model, mirroring
    the CLI --allow-cloud-llm consent gate.
  • Align the form with the API schema: report languages limited to
    en/de/fr/es and formats to pdf/json/html (pt/ja/zh and txt used to
    fail validation), anatomy options matched to the shipped prompt
    regions.
  • Serve the favicon from /static so the strict CSP (default-src 'self')
    keeps blocking data: URIs; keep htmx vendored and zero inline JS.
  • Tests: language switcher, fallback for unknown locales, API-supported
    report languages only, and i18n catalog key parity across locales.

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01GCe7Hb5biAZmtDcVPuqT2z

Summary by CodeRabbit

  • New Features

    • Added a localized MedCheck interface in English, German, French, and Spanish.
    • Introduced a guided analysis wizard with upload, clinical context, anatomy, module, model, and report settings.
    • Added language selection, consent validation, progress updates, results previews, and download options.
    • Improved responsive design, drag-and-drop uploads, keyboard accessibility, and branded navigation.
  • Bug Fixes

    • Unknown languages now fall back to the configured default or English.
    • Added safer handling for analysis results and errors.

…ull UI i18n

Complete UI/UX overhaul of the browser wizard:

- Rebuild index.html on the Metzler design system: design tokens (teal
  palette, graphite text scale, spacing/radius/shadow tokens), 100rem
  container, PDP stepper instead of ad-hoc tabs, floating-label form
  inputs, kit cards/alerts/buttons/badges/file-download components,
  teal-700 footer, and inline stroke icons throughout. All measurements
  in rem, all colors from tokens.
- Localize the entire UI via the existing i18n catalogs: ~70 new ui_*
  keys in en/de/fr/es plus a header language switcher (?lang=, validated
  server-side, falls back to MEDCHECK_LANGUAGE).
- Replace the fake progress simulation with an honest submit flow:
  POST /api/analyze as JSON and surface the real server response
  (501 preview -> warning alert, other errors -> danger alert), with
  text inserted via textContent only.
- Add an explicit cloud-LLM consent checkbox (allow_cloud_llm) that is
  required for cloud models and hidden for the local model, mirroring
  the CLI --allow-cloud-llm consent gate.
- Align the form with the API schema: report languages limited to
  en/de/fr/es and formats to pdf/json/html (pt/ja/zh and txt used to
  fail validation), anatomy options matched to the shipped prompt
  regions.
- Serve the favicon from /static so the strict CSP (default-src 'self')
  keeps blocking data: URIs; keep htmx vendored and zero inline JS.
- Tests: language switcher, fallback for unknown locales, API-supported
  report languages only, and i18n catalog key parity across locales.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCe7Hb5biAZmtDcVPuqT2z
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The web interface now supports English, German, French, and Spanish. The index route selects the locale. The template provides a redesigned three-step wizard. JavaScript submits analyses through JSON and handles consent, progress, results, and errors.

Changes

Localized analysis wizard

Layer / File(s) Summary
Locale catalogs and route wiring
src/medcheck/i18n/*.json, src/medcheck/web/app.py, tests/unit/test_i18n.py
Added matching UI translation keys for four locales. The index route validates the lang parameter and passes localized strings to the template. Tests verify catalog key parity.
Localized wizard interface
src/medcheck/web/templates/index.html
Redesigned the interface with localized navigation, upload and clinical-context fields, anatomy and analysis options, consent controls, result cards, downloads, and footer links.
Client-side wizard and submission flow
src/medcheck/web/static/app.js, tests/unit/test_web.py
Replaced tab simulation with step handling and asynchronous JSON submission. Added consent validation, progress and alert handling, dropzone keyboard support, sticky-header behavior, and locale fallback tests.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant IndexRoute
  participant I18nCatalog
  participant AnalysisAPI
  Browser->>IndexRoute: Request homepage with lang
  IndexRoute->>I18nCatalog: Load selected locale
  I18nCatalog-->>IndexRoute: Return localized strings
  IndexRoute-->>Browser: Render localized wizard
  Browser->>AnalysisAPI: POST analysis payload as JSON
  AnalysisAPI-->>Browser: Return progress, success, warning, or error response
Loading

Possibly related PRs

  • Liohtml/MedCheck#76: Extends earlier internationalization and language-loading work into the localized web wizard.
  • Liohtml/MedCheck#108: Related web and catalog localization changes for the en, de, fr, and es locales.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main UI redesign and full internationalization changes.
Description check ✅ Passed The description clearly covers the redesign, localization, behavior changes, schema alignment, security constraints, and added tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/ui-ux-orchestration-l5ja6l

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.42857% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/medcheck/web/app.py 71.42% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
tests/unit/test_i18n.py (1)

21-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend the parity test to catch keys removed from all catalogs.

The test compares locales against English only. If a key is dropped from all four catalogs, the assertions still pass, and index.html renders an empty string for that label. Jinja2 resolves an unknown attribute to Undefined, which renders as empty text instead of raising.

Add an assertion that every t.<key> reference in index.html exists in the English catalog.

♻️ Proposed additional assertion
 def test_catalogs_have_identical_keys():
     # Every locale must carry the full key set (report + ui_*) so no language
     # silently falls back to English for individual strings.
     keys = {}
     for lang in ("en", "de", "fr", "es"):
         with open(I18N_DIR / f"{lang}.json", encoding="utf-8") as f:
             keys[lang] = set(json.load(f))
     assert keys["de"] == keys["en"]
     assert keys["fr"] == keys["en"]
     assert keys["es"] == keys["en"]
+
+
+def test_template_keys_exist_in_catalog():
+    # Guard against a key that is removed from every catalog at once: Jinja2
+    # would render an empty label instead of raising.
+    import re
+    from pathlib import Path
+
+    template = Path(__file__).parents[2] / "src/medcheck/web/templates/index.html"
+    used = set(re.findall(r"\bt\.([a-zA-Z0-9_]+)", template.read_text(encoding="utf-8")))
+    with open(I18N_DIR / "en.json", encoding="utf-8") as f:
+        available = set(json.load(f))
+    assert used <= available, sorted(used - available)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_i18n.py` around lines 21 - 30, Extend
test_catalogs_have_identical_keys to parse index.html, collect every referenced
t.<key> translation key, and assert those keys are a subset of keys["en"].
Preserve the existing locale parity assertions.
tests/unit/test_web.py (1)

99-104: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the configured-default branch, not only the English clamp.

index() falls back to settings.default_language for unknown ?lang=, then clamps unsupported configured defaults to "en". This test uses default settings, so both branches return English. Add a case with default_language set to a non-English value to ensure the lookup is covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_web.py` around lines 99 - 104, The test
test_homepage_unknown_language_falls_back_to_default currently only exercises
the English default. Add a case that configures settings.default_language to a
supported non-English language, requests an unknown lang value, and asserts the
response uses that configured language and its catalog content, while retaining
coverage for the unsupported-default clamp to English.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/medcheck/web/static/app.js`:
- Around line 86-102: Update submitAnalysis in src/medcheck/web/static/app.js at
lines 86-102 to submit the selected file bytes via FormData or upload it first
and use the returned identifier as source, and include all checked
input[name="modules"] values in the request. In
src/medcheck/web/templates/index.html lines 1108-1151, retain the module tiles
only when their selections are included in the request. In
src/medcheck/web/templates/index.html lines 977-993, wire the dropzone/fileInput
upload to the request so the “Auto-detect active” state reflects an actual
upload, or disable the dropzone and clearly limit the flow to remote URLs.
- Around line 112-132: Update the response handling in the fetch promise to read
the body defensively, only parse valid JSON, and fall back to the response
status text for empty or non-JSON responses while preserving the existing result
flow. Add and use a localized generic-failure message in the catch branch
instead of rendering String(err), including for parser and network failures.
- Around line 104-111: Add a timeout around the fetch flow in the submit handler
so stalled analyze requests reject and reach the existing final/settle handler
that re-enables startBtn. Ensure the timeout is cleared when the request
settles, and preserve the current success and error handling for responses
completed before the timeout.

In `@src/medcheck/web/templates/index.html`:
- Line 379: Update the .pdp-stepper-step:not(.active) .pdp-stepper-label rule so
inactive labels are visually hidden without using display:none, while keeping
them available to assistive technologies and preserving the existing visual
layout.
- Around line 961-964: Update the analyzeForm fallback behavior so it does not
natively submit multipart data to /api/analyze when JavaScript is unavailable.
Point the form at a non-submitting target or add a noscript notice indicating
that JavaScript is required, while preserving the existing JavaScript JSON
submission flow.

---

Nitpick comments:
In `@tests/unit/test_i18n.py`:
- Around line 21-30: Extend test_catalogs_have_identical_keys to parse
index.html, collect every referenced t.<key> translation key, and assert those
keys are a subset of keys["en"]. Preserve the existing locale parity assertions.

In `@tests/unit/test_web.py`:
- Around line 99-104: The test
test_homepage_unknown_language_falls_back_to_default currently only exercises
the English default. Add a case that configures settings.default_language to a
supported non-English language, requests an unknown lang value, and asserts the
response uses that configured language and its catalog content, while retaining
coverage for the unsupported-default clamp to English.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d02c4c39-71a4-442c-b5fa-b92b44bb82bc

📥 Commits

Reviewing files that changed from the base of the PR and between 109378e and 929aa03.

⛔ Files ignored due to path filters (1)
  • src/medcheck/web/static/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (9)
  • src/medcheck/i18n/de.json
  • src/medcheck/i18n/en.json
  • src/medcheck/i18n/es.json
  • src/medcheck/i18n/fr.json
  • src/medcheck/web/app.py
  • src/medcheck/web/static/app.js
  • src/medcheck/web/templates/index.html
  • tests/unit/test_i18n.py
  • tests/unit/test_web.py

Comment on lines +86 to +102
var urlInput = document.getElementById('sourceUrl');
var fileInput = document.getElementById('fileInput');
var source = (urlInput && urlInput.value.trim()) ||
(fileInput && fileInput.files && fileInput.files[0] && fileInput.files[0].name) ||
'browser-upload';

var anatomy = document.getElementById('anatomy');
var language = document.getElementById('reportLanguage');
var format = document.getElementById('reportFormat');

var body = {
source: source,
report_format: format ? format.value : 'json',
language: language ? language.value : 'en',
allow_cloud_llm: !!(cloud && consent && consent.checked)
};
if (anatomy && anatomy.value) body.anatomy = anatomy.value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift

The submitted payload ignores most of the wizard form. submitAnalysis builds a fixed object with source, report_format, language, allow_cloud_llm, and an optional anatomy. The template presents an upload dropzone and six module checkboxes that imply the server receives them. The selected file is reduced to its name, and the module selection is discarded. A user who uploads a scan and selects modules gets an analysis request that contains neither.

  • src/medcheck/web/static/app.js#L86-L102: build the request from the form state instead of a fixed literal. Read modules from the checked input[name="modules"] elements. To transmit the file bytes, send FormData to a multipart endpoint, or upload the file first and send the returned identifier as source. Line 89 currently sends only fileInput.files[0].name, which the server cannot resolve.
  • src/medcheck/web/templates/index.html#L1108-L1151: after the payload carries modules, keep these tiles. Until then, the six checkboxes and the :has(.checkbox-input:checked) highlight at Line 542 show state that has no effect on the request.
  • src/medcheck/web/templates/index.html#L977-L993: the dropzone and #fileInput accept a file, and fileSelected reveals the "Auto-detect active" badge at app.js Lines 35-36. That badge confirms an upload that does not happen. Either wire the upload, or disable the dropzone and state that the browser flow accepts a remote URL only.
📍 Affects 2 files
  • src/medcheck/web/static/app.js#L86-L102 (this comment)
  • src/medcheck/web/templates/index.html#L1108-L1151
  • src/medcheck/web/templates/index.html#L977-L993
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/medcheck/web/static/app.js` around lines 86 - 102, Update submitAnalysis
in src/medcheck/web/static/app.js at lines 86-102 to submit the selected file
bytes via FormData or upload it first and use the returned identifier as source,
and include all checked input[name="modules"] values in the request. In
src/medcheck/web/templates/index.html lines 1108-1151, retain the module tiles
only when their selections are included in the request. In
src/medcheck/web/templates/index.html lines 977-993, wire the dropzone/fileInput
upload to the request so the “Auto-detect active” state reflects an actual
upload, or disable the dropzone and clearly limit the flow to remote URLs.

Comment on lines +104 to +111
if (startBtn) startBtn.disabled = true;
setProgress(30, form.getAttribute('data-msg-sending'));

fetch(form.getAttribute('action'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a request timeout so the submit button cannot stay disabled.

Line 104 disables #startBtn. The settle handler at Line 133 re-enables it. fetch has no default timeout. If the connection stalls, no handler runs, and the button stays disabled for the rest of the page session. The user then has no way to retry without a reload.

The analyze endpoint is rate limited at app.py Line 189 and is described as expensive, so a slow or stalled response is realistic.

🛡️ Proposed fix
     if (startBtn) startBtn.disabled = true;
     setProgress(30, form.getAttribute('data-msg-sending'));
 
+    var controller = new AbortController();
+    var timeoutId = window.setTimeout(function () { controller.abort(); }, 120000);
+
     fetch(form.getAttribute('action'), {
       method: 'POST',
       headers: { 'Content-Type': 'application/json' },
-      body: JSON.stringify(body)
+      body: JSON.stringify(body),
+      signal: controller.signal
     })

Clear the timer in the final handler:

       .then(function () {
+        window.clearTimeout(timeoutId);
         if (startBtn) startBtn.disabled = false;
       });
🧰 Tools
🪛 ast-grep (0.45.0)

[error] 104-104: React's useState should not be directly called
Context: setProgress(30, form.getAttribute('data-msg-sending'))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/medcheck/web/static/app.js` around lines 104 - 111, Add a timeout around
the fetch flow in the submit handler so stalled analyze requests reject and
reach the existing final/settle handler that re-enables startBtn. Ensure the
timeout is cleared when the request settles, and preserve the current success
and error handling for responses completed before the timeout.

Comment on lines +112 to +132
.then(function (resp) {
return resp.json().then(function (data) {
return { ok: resp.ok, status: resp.status, data: data };
});
})
.then(function (result) {
var detail = result.data && result.data.detail;
if (typeof detail !== 'string') detail = JSON.stringify(result.data);
if (result.ok) {
setProgress(100, '');
showResultAlert('success', detail);
} else {
setProgress(0, form.getAttribute('data-msg-waiting'));
// 501 = known preview limitation -> warning; anything else -> danger.
showResultAlert(result.status === 501 ? 'warning' : 'danger', detail);
}
})
.catch(function (err) {
setProgress(0, form.getAttribute('data-msg-waiting'));
showResultAlert('danger', String(err));
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

A non-JSON response surfaces a raw parser message to the user.

Line 113 calls resp.json() for every response. A 502 HTML error page from a proxy, a 204 with an empty body, or any non-JSON error body rejects that promise. Control passes to the .catch at Line 129, and Line 131 renders String(err), for example SyntaxError: Unexpected token < in JSON at position 0.

The user sees an internal parser string instead of a localized message. Read the body defensively and fall back to the status text.

🛡️ Proposed fix
       .then(function (resp) {
-        return resp.json().then(function (data) {
-          return { ok: resp.ok, status: resp.status, data: data };
-        });
+        return resp.text().then(function (raw) {
+          var data = null;
+          try { data = JSON.parse(raw); } catch (e) { data = null; }
+          return { ok: resp.ok, status: resp.status, data: data };
+        });
       })
       .then(function (result) {
         var detail = result.data && result.data.detail;
-        if (typeof detail !== 'string') detail = JSON.stringify(result.data);
+        if (typeof detail !== 'string') {
+          detail = 'HTTP ' + result.status;
+        }
         if (result.ok) {

Add a localized generic-failure key so the .catch branch at Line 131 does not print String(err) either.

🧰 Tools
🪛 ast-grep (0.45.0)

[error] 120-120: React's useState should not be directly called
Context: setProgress(100, '')
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 123-123: React's useState should not be directly called
Context: setProgress(0, form.getAttribute('data-msg-waiting'))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)


[error] 129-129: React's useState should not be directly called
Context: setProgress(0, form.getAttribute('data-msg-waiting'))
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(usestate-direct-usage)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/medcheck/web/static/app.js` around lines 112 - 132, Update the response
handling in the fetch promise to read the body defensively, only parse valid
JSON, and fall back to the response status text for empty or non-JSON responses
while preserving the existing result flow. Add and use a localized
generic-failure message in the catch branch instead of rendering String(err),
including for parser and network failures.

.pdp-stepper-step.completed { color: rgba(255, 255, 255, 0.85); }
.pdp-stepper-num { font-weight: 700; }
.pdp-stepper-label { font-weight: 400; }
.pdp-stepper-step:not(.active) .pdp-stepper-label { display: none; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Inactive stepper buttons expose only a number as their accessible name.

display: none removes the label from the accessibility tree. Each inactive step button then announces only "2." or "3.". A screen-reader user cannot identify the target step before activating it.

Keep the label available to assistive technology and hide it visually only.

♿ Proposed fix
-    .pdp-stepper-step:not(.active) .pdp-stepper-label { display: none; }
+    .pdp-stepper-step:not(.active) .pdp-stepper-label {
+      position: absolute;
+      width: 0.0625rem;
+      height: 0.0625rem;
+      padding: 0;
+      overflow: hidden;
+      clip-path: inset(50%);
+      white-space: nowrap;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.pdp-stepper-step:not(.active) .pdp-stepper-label { display: none; }
.pdp-stepper-step:not(.active) .pdp-stepper-label {
position: absolute;
width: 0.0625rem;
height: 0.0625rem;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/medcheck/web/templates/index.html` at line 379, Update the
.pdp-stepper-step:not(.active) .pdp-stepper-label rule so inactive labels are
visually hidden without using display:none, while keeping them available to
assistive technologies and preserving the existing visual layout.

Comment on lines +961 to +964
<form id="analyzeForm" action="/api/analyze" method="post" enctype="multipart/form-data"
data-msg-consent="{{ t.ui_consent_required }}"
data-msg-sending="{{ t.ui_prog_sending }}"
data-msg-waiting="{{ t.ui_progress_waiting }}">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The form still declares a native multipart submission that the API cannot accept.

app.js calls e.preventDefault() at Line 194 and posts a JSON body at Lines 107-111. The enctype="multipart/form-data" attribute therefore has no effect on the normal path.

If JavaScript is disabled or /static/app.js fails to load, the browser performs the native POST /api/analyze as multipart. AnalyzeRequest expects a JSON body, so the request returns 422 and replaces the wizard with a raw error page.

Point the form at a non-submitting target, or add a <noscript> notice that the browser flow requires JavaScript.

♻️ Proposed change
-        <form id="analyzeForm" action="/api/analyze" method="post" enctype="multipart/form-data"
+        <form id="analyzeForm" action="/api/analyze" method="post"
               data-msg-consent="{{ t.ui_consent_required }}"
               data-msg-sending="{{ t.ui_prog_sending }}"
               data-msg-waiting="{{ t.ui_progress_waiting }}">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/medcheck/web/templates/index.html` around lines 961 - 964, Update the
analyzeForm fallback behavior so it does not natively submit multipart data to
/api/analyze when JavaScript is unavailable. Point the form at a non-submitting
target or add a noscript notice indicating that JavaScript is required, while
preserving the existing JavaScript JSON submission flow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants