From e857c371755f8222c38cc59d04abde2acdd687e5 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Sat, 17 Oct 2026 13:51:20 -0400 Subject: [PATCH 01/11] Align filing entry and contextual controls with Miro vision --- .../miro-vision-alignment-2026-10-17.md | 112 ++++++++++++++++++ efile_app/efile/services/filing_path.py | 14 +-- .../efile/static/js/extraction-review.js | 20 +++- efile_app/efile/static/js/upload-documents.js | 15 ++- .../efile/components/profile_header.html | 21 +--- .../templates/efile/extraction_review.html | 56 ++++----- efile_app/efile/templates/efile/login.html | 4 +- efile_app/efile/templates/efile/options.html | 36 ++---- efile_app/efile/templates/efile/parties.html | 11 -- .../efile/templates/efile/party_details.html | 10 ++ efile_app/efile/templates/efile/register.html | 6 +- efile_app/efile/templates/efile/review.html | 8 +- .../templates/efile/upload_documents.html | 37 +++--- efile_app/efile/tests/test_ai_opt_out.py | 8 +- efile_app/efile/tests/test_filer_role.py | 20 ++++ .../tests/test_filing_path_navigation.py | 16 ++- efile_app/efile/tests/test_navigation_menu.py | 14 +-- efile_app/efile/views/draft_views.py | 19 ++- efile_app/efile/views/extraction_review.py | 23 ++-- 19 files changed, 285 insertions(+), 165 deletions(-) create mode 100644 docs/developer-notes/miro-vision-alignment-2026-10-17.md diff --git a/docs/developer-notes/miro-vision-alignment-2026-10-17.md b/docs/developer-notes/miro-vision-alignment-2026-10-17.md new file mode 100644 index 00000000..eeb80aa7 --- /dev/null +++ b/docs/developer-notes/miro-vision-alignment-2026-10-17.md @@ -0,0 +1,112 @@ +# Miro wireframe alignment review + +Reviewed 2026-10-17 against the 18 device-screen containers on [E-filing Wireframe](https://miro.com/app/board/uXjVEabHGug=/), using the signed-in browser's accessible outline and a visual check of Confirm case / Organize documents. The connector still denied access. Baseline: clean local `main`; branch: `codex/miro-vision-alignment`. + +## Scope and design intent + +Use short instructions, “documents” and “e-file” consistently, pop-ups for short optional definitions, and disclosures for longer optional help. Preserve the existing eight stages. The board uses Easy File branding; confirm before replacing LITEfile or jurisdiction branding. + +This is not a complete flow specification. It contains alternative home/start layouts and questions about removing the new/existing distinction, clickable progress, and fee-waiver timing. Sample errors and duplicate party names are examples, not behavior to reproduce. Illinois-specific material must remain jurisdiction-specific. + +## Screen-by-screen comparison + +Implementation paths below are relative to `efile_app/efile/`. “Proposed” items are not implemented. + +| Board frame | Implementation | Required changes / current alignment | +| --- | --- | --- | +| General design/information sharing | Shared header, footer, workflow base | Proposed: consistent information hierarchy and optional help; confirm Easy File branding. Get more help/Call already exist where configured, including Illinois Court Help. The simplified footer proposal differs from the fuller footers in other frames: resolve before removing GitHub, Call or credit. | +| Landing / Sign in | `templates/efile/login.html` | Implemented “Sign in” heading and existing-account instruction with state parameter. Email/password, Forgot password, Sign in and Create an account already match. Proposed: Learn more about e-filing link/destination. Keep password visibility. | +| Create an account | `templates/efile/register.html`, `forms.py` | Implemented “Create an account”, “Name”, “Address”. Proposed: “Create an account to e-file in [state]”, contact-reuse note, explicit optional middle-name label. Preserve address line 2 and validation despite omissions in drawing. | +| Activation | `views/register.py`, login messages | Already redirects to sign-in with an activation message. No new route needed. Verify provider email subject/link before adopting exact quoted wording from the board across states. | +| Home options: new case; existing case/draft | `templates/efile/options.html`, `views/draft_views.py` | Implemented My filings / View filings and status description. Proposed: one Start filing card, visible View drafts/count instead of collapsed resume controls, concise state-specific welcome. Keep plans accessible through Menu and preserve fresh-draft isolation. | +| Start: What are you trying to do? | `templates/efile/filing_path.html` | New/existing/unsure options already exist. Small remaining rename: File in → File into. Board asks whether this question can go; recommend deferring to Confirm information, not silently inferring the answer. Awaiting decision. | +| Start: What do you want to do? New account; existing account/filing | Options and workflow progress | Treat as alternative home/start designs, not two new sequential screens. Choose one canonical home with conditional draft card and Find documents/help. Do not repeat a new-sign-in banner on every return. | +| Upload: no documents; rules/AI/documents uploaded | `templates/efile/upload_documents.html` | Implemented Upload your documents, Choose PDF or Word files or drag them here, Learn more about how we use AI. Proposed: concise branch instruction/Change, less routine size/readability/preparation copy, remove redundant document-role sublabels. Keep validation/errors and conversion/extraction states. AI explainer content already matches. Court-notice title must describe actual state notices. | +| Upload navigation | Upload → Confirm information | Board conflicts: empty state says Preview your PDFs, populated state Confirm information. Recommend current Confirm information plus inline PDF preview. A separate Preview screen would change workflow; the legacy route currently maps to Confirm. | +| Confirm case | `templates/efile/extraction_review.html`, corresponding view/script | Proposed: simplified summary; move people editing to People; retain PDF/Change files; section heading Confirm the details used for this filing; code search after selectors; case-number help where lookup occurs. Drawing combines case number and new-case classifications, unlike the app's separate existing-case lookup. Do not copy that mixed state literally. Preserve partial-read/failure/AI-off states. | +| Organize documents | `templates/efile/organize_documents.html` | Mostly matches: main document, name, filing type, confidentiality, component, optional services/courtesy email, PDF. Trim routine guidance; preserve conditional warnings. Single-document example does not imply removing main-document selection or reorder controls for multiple documents. | +| People: your information | `templates/efile/your_information.html`, UI text catalog | Proposed concise Check your name and contact information plus Why do you need this? explainer. Fields and Continue to parties largely match. Keep jurisdiction-specific required fields/state restrictions. | +| People: who is involved | `templates/efile/parties.html`, `party_details.html`, people services | Role, Save role, Add another person, list/Edit/Continue exist. Proposed destination for extracted-name/side/identity editing; removal inside Edit. Preserve deduplication, organizations, representation, notice recipient, required parties and court-owned records. Do not use You already told us when no people were supplied. | +| Fees: fee waiver/add; choose credit card | `templates/efile/payment.html`, payment/waiver scripts | Most controls match. Keep detailed state-specific eligibility behind short trigger; do not reduce benefits/hardship criteria to income only. Preserve exemptions/zero-fee states, waiver PDF review, account removal, bank retry and fee confirmation gates. | +| Review | `templates/efile/review.html` | Implemented short review/Edit instruction. Existing sections, markers, PDFs, court requirements and consent mostly match. Retain missing Documents Edit, conditional questions/correction/plan warnings and Submit and pay when payable. Sample provider errors are not normal copy. | + +## Controls absent from the wireframes and required destinations + +Absence is not proof of intentional removal. The following must remain reachable or be explicitly redesigned. + +| Current control | Recommended destination / dependency | +| --- | --- | +| Home/Menu separate new/existing Begin buttons | Generic Start filing must still create a fresh draft. Ask the branch on Confirm before code selection. Unspecified path currently redirects to the separate question: changing the label alone is insufficient. | +| Upload Add or replace a document select | Per-document Replace or secondary Upload disclosure. Preserve replacement ID, saved filing choices, extraction reset and new PDF review. Remove+upload is not equivalent. | +| Remember this AI choice and saved account preference | Secondary preference disclosure or account settings. No other screen currently manages the account-wide preference. Preserve scope, reversibility and visibility; never enable AI implicitly. | +| Upload Find forms and instructions | Home/Menu or compact Upload help link; preserve draft/return context. Alternative Start frames include Find documents. | +| Confirm names/sides/add/remove/This is me | Move review and persistence to People, including identity matching. Change Confirm POST handling together with UI; omitted rows must not erase saved people. | +| Confirm I checked these details checkbox | Keep beside PDF, or explicitly redesign Continue and server acknowledgement. `needs_acknowledgement` rejects missing `reviewed_extraction=yes`; deleting HTML alone blocks progression. Prepared-PDF fingerprint approval is a separate safeguard that must also remain. | +| Confirm new/existing Change/radios | Keep concise correction and undecided-state choice. Branch changes invalidate incompatible codes, case identity and fees. | +| Confirm Which side of this case are you on? | Before Check documents or in that screen's existing fallback. Side-specific document plans need the answer before People. | +| Existing-case suggested filing type | Keep editable proposal on Confirm or move to scoped Organize filing-type search; it is not yet a court-approved code. | +| Registration street address 2 | Keep optional apartment/unit field or equivalent address component. | +| People This is me / Replace with me / Add me as a party | Preserve conditional identity/name-choice controls before matching. Prevent duplicate account/extracted parties. | +| People Remove party | Inside Edit or overflow control, preserving court-owned-party restrictions. | +| Who are you filing for? / Email for court notices | Conditional People panel for representation and existing cases. The board only draws the not-a-party radio, not its necessary follow-up. | +| Multiple-document main selection / move up/down | Retain for multiple documents; add missing state to design. Single-document omission needs no relocation. | +| Payment Remove account | Compact Manage payment methods or per-account action. No alternate management screen exists. Preserve confirmation and selection clearing. | +| Waiver This copy looks right / Remove and upload a different file | Post-upload preview/check state next to waiver upload. Preserve prepared-file review fingerprints. | +| Review Documents Edit | Keep Organize link with `return_to=review` and access to change files. Otherwise name/type/confidentiality cannot be corrected from this summary. | +| Review Submit and pay | Keep when confirmed fees are payable; board's universal Submit filing obscures payment consequence. Preserve fresh fee and consent gates. | +| Profile sign out/change state | Retain account menu actions; only icon/Menu are drawn. | + +## Missing designs versus missing app screens + +- Check documents is stage 4 but has no frame. Existing implementation includes plans, side-specific lists, attach/detach, progress and PDF checks. Do not remove the stage. +- Existing-case lookup, no-match/failure, confirmation and imported parties need designs; routes already exist. +- Add/edit person, organizations, required parties, duplicate-name claim and representation need designs; components/routes already exist. +- Case questions/amount in controversy, plans/document finding, drafts, filing status/detail, corrections and successful submission/tracking are not drawn. Their omission does not authorize removal. +- State designs needed: registration/reset errors, extraction failure/partial/AI off, rejection/conversion, multiple documents, payment account creation/removal, unavailable banks, waiver preview and blocked submission. +- New UI work likely needed: contact-purpose explainer, case-number help, consolidated home and approved secondary controls. Activation does not require a new route. +- Progress is currently noninteractive (`components/workflow_progress.html` renders spans). Board asks for clickable stages: recommend completed-stage links with save/dirty-form handling and existing branch prerequisites, not arbitrary jumps past validation. + +## Copy to reduce versus conditional guidance to retain + +Reduce routine preparation/requirements text, document-role sublabels, repeated introductions and always-visible contact-purpose explanation. Use optional disclosures and short contextual errors. Do not delete partial extraction, branch/court change, confidentiality, court-owned record, eligibility, unavailable-filing or error messages because the happy-path frame omits them. Court acknowledgements and final consent are functional controls. The board retains notices at Upload and Review. + +## Decisions requested + +1. One Start filing with branch selection on Confirm (recommended), separate question before Upload, or existing split buttons. +2. Move people controls to People and retain explicit review acknowledgement (recommended), replace acknowledgement with Continue confirmation, or retain current layout. +3. Preserve omitted actions in compact secondary controls (recommended), or retain current placement. + +Also unresolved: Easy File branding, canonical home variant, scope of clickable progress, and whether a standalone PDF preview is intended. These initial questions were answered; see the approved implementation below. + +## Initial copy-only changes and verification + +Five template-only files changed: login, register, upload_documents, options, review. No routes, input names, persistence, validation, payment behavior or migrations changed. + +- WSL server running at http://127.0.0.1:8000. Updated login/registration observed in browser; fields and navigation intact. +- 64 tests passed: interface consistency, reorganized start, review submission flow, AI opt-out. +- Used `uv sync --group dev`, then `uv run --group dev python -m pytest`; existing pytest executable could not spawn. +- Server started without extraction/index workers or LocalStack startup, and without migrations. Existing `EFSP_TEST_DOCUMENT_URL` substitutes a sample PDF on submission. No live filing/payment; authenticated end-to-end upload/fee flow not browser-verified. +- Copy remains in translation tags; catalogs need normal localization follow-up. English verified. + + +## Approved implementation after review + +The user approved one Start filing action with the branch decision at Confirm information, and compact secondary controls. They clarified that detected roles must be confirmed immediately next to the detection; only undetected roles may be deferred. + +- Home and Menu now offer one Start filing action. Generic starts create a fresh, blank-branch draft and go directly to Upload. Back returns to Home. Explicit-path legacy starts and plan starts remain compatible. +- Confirm information asks new/existing before exposing new-case classifications. Detected names, sides and This is me remain beside the extraction, honoring the clarification rather than moving all identity matching to People. +- A side-specific role suggestion must still be confirmed immediately. Without a suggestion or an already confirmed role, that question is deferred; Check documents asks if its checklist needs the side. Normal party details continue on People. +- Document-review acknowledgement remains and now sits beside the PDF previews. +- Upload replacement is in Replace a document. Saved AI preference is in AI preferences for future filings; opening it does not save a preference. Changing the checkbox saves through the existing endpoint. +- Party removal is in the party Edit screen under More options for this person or organization, posting to the same protected removal endpoint with return context. +- Review's document Edit link is under Document options and retains return-to-review behavior. +- Branding, broader layout changes, payment-method management, clickable progress and a standalone preview screen remain outside this approved implementation. + +## Verification of the approved implementation + +- Broad regression run: 267 passed, 2 skipped, and 2 stale UI assertions failed. Updated those assertions for the renamed path label and native AI-preference disclosure; both passed on rerun. +- Final focused run: 75 passed, including both corrected assertions, navigation, role confirmation/deferral, reorganized start, document previews and interface consistency. +- Ruff, JavaScript syntax checks for both changed scripts, and git diff whitespace checks passed. +- In-app browser inspection of Django-rendered synthetic previews confirmed one Start filing button and working native disclosures for replacement and saved AI preference. These were layout checks with scripts removed, not authenticated end-to-end tests. Temporary synthetic account/session data was removed. +- A separate headless browser check was rejected by automatic approval review with only “blocked by policy”; it was not retried. Authenticated upload, extraction, payment and submission still require an end-to-end browser pass. No live filing or payment was performed. +- Implementation branch: codex/miro-vision-alignment. The WSL development app was checked at http://127.0.0.1:8000 with the limitations described above. diff --git a/efile_app/efile/services/filing_path.py b/efile_app/efile/services/filing_path.py index 2d329510..b97b82d7 100644 --- a/efile_app/efile/services/filing_path.py +++ b/efile_app/efile/services/filing_path.py @@ -1,13 +1,11 @@ """The new-or-existing-case answer: where it came from, and changing it. -A filer can say which kind of filing this is in three places -- the start -menu's "Start a new case" / "File into an existing case", a filing plan, or the -"What are you trying to do?" question -- and can correct it on Confirm case -once their documents have been read. Two things follow from that: - -* Back and the progress steps should follow the way the filer actually came - in. Someone who chose from the start menu never saw the question, and Back - from Upload should not introduce it as if they had. +Ordinary starts upload first and choose new/existing on Confirm information. +Older explicit-path starts, filing plans and the standalone correction question +remain supported. + +* Back follows the way the filer came in. Generic starts and explicit menu + starts return from Upload to Home, without introducing an unseen question. * Changing the answer is more than one field. The same draft and the same PDFs carry on, but what was chosen for the other kind of filing -- the court's filing types differ between opening a case and filing into one, an diff --git a/efile_app/efile/static/js/extraction-review.js b/efile_app/efile/static/js/extraction-review.js index 22312ccc..1a61ada9 100644 --- a/efile_app/efile/static/js/extraction-review.js +++ b/efile_app/efile/static/js/extraction-review.js @@ -517,8 +517,10 @@ console.warn("Could not load the sides of this case:", error); } if (token !== roleGeneration) return; - roleOptions.innerHTML = roles.map(roleOptionHtml).join(""); - roleField.hidden = roles.length === 0; + if (!roles.some((role) => role.id === savedRole)) savedRole = ""; + const needsConfirmation = Boolean(savedRole) || roles.some((role) => role.suggested); + roleOptions.innerHTML = needsConfirmation ? roles.map(roleOptionHtml).join("") : ""; + roleField.hidden = !needsConfirmation; } const ADVANCE = { @@ -665,8 +667,9 @@ proposal.disabled = !existing; } const details = document.getElementById("new-case-details"); - details.hidden = existing; - details.disabled = existing; + const isNew = form.querySelector('input[name="existing_case"]:checked')?.value === "new"; + details.hidden = !isNew; + details.disabled = !isNew; const parties = document.getElementById("review-parties"); if (parties) { parties.hidden = existing; @@ -710,6 +713,15 @@ form.addEventListener("submit", (event) => { if (isExisting()) return; + if (!form.querySelector('input[name="existing_case"]:checked')) { + event.preventDefault(); + errorBox.textContent = gettext("Choose whether this is a new or existing court case to continue."); + errorBox.hidden = false; + document.getElementById("path-question").scrollIntoView({ + block: "center" + }); + return; + } if (blockUnfinished(event)) return; const isNew = form.querySelector('input[name="existing_case"]:checked')?.value === "new"; const missingCase = isNew && (!fields.court.select.value || !fields.case_category.select.value || !fields.case_type.select.value); diff --git a/efile_app/efile/static/js/upload-documents.js b/efile_app/efile/static/js/upload-documents.js index 3ee7d1b8..26b04cda 100644 --- a/efile_app/efile/static/js/upload-documents.js +++ b/efile_app/efile/static/js/upload-documents.js @@ -90,9 +90,8 @@ waitTimerDone = true; }, Number(form.dataset.waitSeconds) * 1000); } - // The "remember this" row is offered only after the filer changes the - // setting, and only for the rest of this page load. Until then the account - // preference is not this request's business, so it is left out of the post. + // Merely opening preferences never changes the account default. Include + // it only after the filer changes an AI setting during this page load. let rememberOffered = false; function aiIsOff() { @@ -122,10 +121,7 @@ function offerToRemember() { if (!aiRemember || rememberOffered) return; rememberOffered = true; - if (window.bootstrap) new window.bootstrap.Collapse(aiRemember, { - toggle: false - }).show(); - else aiRemember.classList.add("show"); + aiRemember.open = true; } async function saveAiPreference() { @@ -164,7 +160,10 @@ }); } - if (aiRememberChoice) aiRememberChoice.addEventListener("change", saveAiPreference); + if (aiRememberChoice) aiRememberChoice.addEventListener("change", () => { + rememberOffered = true; + saveAiPreference(); + }); function syncFiles() { const transfer = new DataTransfer(); diff --git a/efile_app/efile/templates/efile/components/profile_header.html b/efile_app/efile/templates/efile/components/profile_header.html index ada140cb..39f471f4 100644 --- a/efile_app/efile/templates/efile/components/profile_header.html +++ b/efile_app/efile/templates/efile/components/profile_header.html @@ -38,30 +38,13 @@
-

{% translate "My cases" %}

-

{% translate "Check filing status and get copies of your documents." %}

+

{% translate "My filings" %}

+

{% translate "See the status of all documents you have e-filed with the court." %}

{% translate "View cases" %} + class="btn btn-primary">{% translate "View filings" %}
{% endif %} diff --git a/efile_app/efile/templates/efile/parties.html b/efile_app/efile/templates/efile/parties.html index 37df7750..d37a5526 100644 --- a/efile_app/efile/templates/efile/parties.html +++ b/efile_app/efile/templates/efile/parties.html @@ -275,17 +275,6 @@

{% translate "Party list" %}

{% if item.party.source != "court" %} {% translate "Edit" %} -
- {% csrf_token %} - - - - -
{% endif %} {% endif %} diff --git a/efile_app/efile/templates/efile/party_details.html b/efile_app/efile/templates/efile/party_details.html index 831e2224..7a730e64 100644 --- a/efile_app/efile/templates/efile/party_details.html +++ b/efile_app/efile/templates/efile/party_details.html @@ -258,6 +258,16 @@

+
+ {% translate "More options for this person or organization" %} +
+ {% csrf_token %} + + + + +
+
{% if not filer_is_party and claimable %} {% comment %} Offered for a party the document named as much as for a blank row: diff --git a/efile_app/efile/templates/efile/register.html b/efile_app/efile/templates/efile/register.html index 4fe7ee3e..298d673b 100644 --- a/efile_app/efile/templates/efile/register.html +++ b/efile_app/efile/templates/efile/register.html @@ -16,7 +16,7 @@ {% endblock extra_css %} {% block auth_content %}
-

{% translate "Create an eFile account" %}

+

{% translate "Create an account" %}

@@ -43,7 +43,7 @@

{% translate "Create an eFile account
{% csrf_token %} -
{% translate "Your legal name" %}
+
{% translate "Name" %}
{{ form.first_name.label_tag }} {{ form.first_name }} @@ -59,7 +59,7 @@

{% translate "Create an eFile account {% if form.last_name.errors %}
{{ form.last_name.errors }}
{% endif %}

-
{% translate "Physical address" %}
+
{% translate "Address" %}
{{ form.street_address.label_tag }} {{ form.street_address }} {% if form.street_address.errors %}
{{ form.street_address.errors }}
{% endif %} diff --git a/efile_app/efile/templates/efile/review.html b/efile_app/efile/templates/efile/review.html index 9aadbcd6..6a9e616f 100644 --- a/efile_app/efile/templates/efile/review.html +++ b/efile_app/efile/templates/efile/review.html @@ -23,6 +23,7 @@

Before you can submit

{% endif %} {% endif %} +

{% translate "Check each section carefully. If you need to make changes, use Edit to go back to the screen where you entered it." %}

{% include "efile/partials/filing_guidance.html" %} {% if extracted_markers.has_any %}
@@ -89,8 +90,11 @@

{% translate "Case" %}

{% translate "Documents" %}

- {% translate "Edit" %} +
+ {% translate "Document options" %} + {% translate "Edit" %} +
{% include "efile/partials/correction_section_issues.html" with section="documents" %}
diff --git a/efile_app/efile/templates/efile/upload_documents.html b/efile_app/efile/templates/efile/upload_documents.html index 87e1fd5a..b4249370 100644 --- a/efile_app/efile/templates/efile/upload_documents.html +++ b/efile_app/efile/templates/efile/upload_documents.html @@ -7,7 +7,7 @@ {% endblock title %} {% block workflow_content %}
-

{% translate "Upload your court documents" %}

+

{% translate "Upload your documents" %}

{% include "efile/partials/filing_guidance.html" %} {% if saved_path %}

@@ -48,15 +48,18 @@

{% translate "Upload your court documents" %}

data-wait-seconds="{{ extraction_wait_seconds }}"> {% csrf_token %} {% if documents %} - - -

To replace a document, select it above and choose one new file. Its filing choices stay saved. Review the new PDF before submitting.

+
+ {% translate "Replace a document" %} + + +

To replace a document, select it above and choose one new file. Its filing choices stay saved. Review the new PDF before submitting.

+
{% endif %}
{% translate "Upload your court documents" %}

aria-describedby="upload-requirements upload-preparation" multiple /> - + {% translate "PDF or Word (DOC or DOCX)" %} {% translate "10 MB per file" %} @@ -130,16 +133,16 @@

{% translate "Selected files" %}

type="button" id="ai-explainer" aria-label="{% translate "How do we use AI? Read our explanation." %}"> - {% translate "How do we use AI?" %} + {% translate "Learn more about how we use AI." %} {% comment %} - Offered only once the filer has actually changed the choice above, - and only for the rest of that page load: someone who never touches - the setting is not asked to make a decision about it. + Account preferences are optional secondary controls. Merely opening + this disclosure does not change the saved preference. {% endcomment %} -
+
+ {% translate "AI preferences for future filings" %}
{% translate "Selected files" %} {% translate "Remember this choice and use it for all my future filings" %}
-
+ {% comment %} Both states of the choice are written here so each one is a translated string rather than something JavaScript assembles. The diff --git a/efile_app/efile/tests/test_ai_opt_out.py b/efile_app/efile/tests/test_ai_opt_out.py index f47f7dcc..c863af15 100644 --- a/efile_app/efile/tests/test_ai_opt_out.py +++ b/efile_app/efile/tests/test_ai_opt_out.py @@ -241,13 +241,13 @@ def test_review_screen_credits_the_keyword_search_rather_than_a_reading(client, @pytest.mark.django_db -def test_the_remember_row_is_not_offered_until_the_filer_changes_the_setting(client, opted_out_draft): +def test_saved_ai_preference_is_available_in_a_closed_disclosure(client, opted_out_draft): page = client.get(upload_url()).content.decode() - # It is in the markup, closed: the script opens it on the first change, and - # a filer who never touches the setting is never asked about their account. + # The optional preference is discoverable without changing the filing choice. assert 'id="ai-remember"' in page - assert 'class="collapse ai-remember"' in page + assert '
' in page + assert "AI preferences for future filings" in page assert "Remember this choice" in page assert 'id="ai-remember-choice" />' in page diff --git a/efile_app/efile/tests/test_filer_role.py b/efile_app/efile/tests/test_filer_role.py index 322ca2dc..face26b9 100644 --- a/efile_app/efile/tests/test_filer_role.py +++ b/efile_app/efile/tests/test_filer_role.py @@ -304,3 +304,23 @@ def test_without_a_side_the_party_type_is_still_guessed_from_posture(signed_in): ] assert guessed_party_type(signed_in, party_types)["code"] == "PL" + + +@pytest.mark.django_db +def test_no_detected_side_defers_to_the_document_checklist(client, signed_in): + response = confirm_filing(client, filing_type_name="Other filing") + + signed_in.refresh_from_db() + assert response.status_code == 302 + assert response.url.partition("?")[0] == CHECKLIST_URL + assert signed_in.filer_role == "" + page = client.get(response.url) + assert page.context["choosing_filer_role"] is True + + +@pytest.mark.django_db +def test_deferred_side_cannot_accept_an_unoffered_role(client, signed_in): + response = confirm_filing(client, filing_type_name="Other filing", filer_role="invalid") + assert response.status_code == 200 + signed_in.refresh_from_db() + assert signed_in.filer_role == "" diff --git a/efile_app/efile/tests/test_filing_path_navigation.py b/efile_app/efile/tests/test_filing_path_navigation.py index 60e5afe4..07f694f1 100644 --- a/efile_app/efile/tests/test_filing_path_navigation.py +++ b/efile_app/efile/tests/test_filing_path_navigation.py @@ -220,7 +220,7 @@ def test_upload_shows_the_saved_answer_with_a_change_link(signed_in): content = signed_in.get(UPLOAD_URL).content.decode() - assert "Start a new case" in content + assert "Starting a new case" in content assert f'href="{FILING_PATH_URL}' in content assert "Change whether this is a new or existing case" in content @@ -435,3 +435,17 @@ def test_new_or_existing_comes_before_the_case_fields_with_the_case_number_besid fields = content.index('class="review-grid"') category = content.index('id="case_category_code"') assert section < question < case_number < fields < category + + +@pytest.mark.django_db +def test_generic_start_keeps_back_at_home_and_branch_unset(signed_in): + response = signed_in.post(START_URL, {}) + draft = FilingDraft.objects.latest("pk") + assert response.url.partition("?")[0] == UPLOAD_URL + assert draft.existing_case == "" + assert back_link(signed_in.get(response.url).content.decode()) == OPTIONS_URL + assert WorkflowStepKey.FILING_PATH not in [step.key for step in get_visible_workflow(draft)] + reviewed_document(draft=draft, role=FilingDocument.Role.LEAD, name="document.pdf") + confirm_page = signed_in.get(CONFIRM_URL) + assert confirm_page.context["path_question_open"] + assert confirm_page.context["chosen_existing_case"] == "" diff --git a/efile_app/efile/tests/test_navigation_menu.py b/efile_app/efile/tests/test_navigation_menu.py index 0ea83775..b03ad62a 100644 --- a/efile_app/efile/tests/test_navigation_menu.py +++ b/efile_app/efile/tests/test_navigation_menu.py @@ -39,10 +39,10 @@ def test_every_signed_in_screen_carries_the_filing_menu(client, user): content = client.get(OPTIONS_URL).content.decode() - assert "Start a new case" in content - assert "File into an existing case" in content + assert "Start filing" in content + assert "File into an existing case" not in content assert "My draft e-filings" in content - assert "My cases" in content + assert "My filings" in content assert "My filing plans" in content assert reverse("my_drafts", kwargs={"jurisdiction": "illinois"}) in content assert reverse("filing_statuses", kwargs={"jurisdiction": "illinois"}) in content @@ -56,7 +56,7 @@ def test_the_menu_is_there_part_way_through_a_filing_too(client, user): content = client.get(FILING_PATH_URL).content.decode() - assert "Start a new case" in content + assert "Start filing" in content assert "My draft e-filings" in content assert START_URL in content @@ -89,12 +89,12 @@ def test_starting_a_filing_from_the_menu_knows_which_kind_it_is(client, user, ch @pytest.mark.django_db -def test_starting_a_filing_without_saying_which_kind_asks(client, user): +def test_generic_start_defers_the_branch_until_confirm(client, user): sign_in(client, user) response = client.post(START_URL, {}) - assert response.url == f"{FILING_PATH_URL}?draft={FilingDraft.objects.get(user=user).pk}" + assert response.url == f"{UPLOAD_URL}?draft={FilingDraft.objects.get(user=user).pk}" assert FilingDraft.objects.get(user=user).existing_case == "" @@ -114,7 +114,7 @@ def test_starting_a_filing_never_reopens_the_last_one(client, user): session[CURRENT_DRAFT_SESSION_KEY] = old.pk session.save() - client.post(START_URL, {"existing_case": "new"}) + client.post(START_URL, {}) current_id = client.session[CURRENT_DRAFT_SESSION_KEY] assert current_id != old.pk diff --git a/efile_app/efile/views/draft_views.py b/efile_app/efile/views/draft_views.py index 4a1027a3..6d55bc96 100644 --- a/efile_app/efile/views/draft_views.py +++ b/efile_app/efile/views/draft_views.py @@ -19,16 +19,10 @@ @require_http_methods(["POST"]) def start_filing(request, jurisdiction): - """Start a filing from anywhere, already knowing which kind it is. + """Start a fresh draft; ordinary starts choose new/existing after upload. - The navigation menu offers "Start a new case" and "File into an existing - case" as two destinations, because that is how filers describe what they - are about to do. Both land on the same workflow; naming the path here just - saves answering the first question again. - - Starting a filing always makes a new one. Nothing is carried over from the - filing the browser was last pointed at, which is the whole point of the - menu item: it is how a filer gets out of a draft they no longer want. + Explicit paths from older links remain supported. Neither kind of start + carries answers over from the draft the user was previously working on. """ if not request.user.is_authenticated or not get_tyler_token(request, jurisdiction): @@ -39,7 +33,7 @@ def start_filing(request, jurisdiction): draft = create_current_draft( request, jurisdiction, - current_step=WorkflowStepKey.FILING_PATH, + current_step=WorkflowStepKey.UPLOAD_DOCUMENTS, workflow_version=2, ) logger.info("Started draft id=%s from the navigation menu (path=%s)", draft.pk, path or "unset") @@ -48,8 +42,9 @@ def start_filing(request, jurisdiction): write_case_data(draft, {"existing_case": path}, current_step=WorkflowStepKey.UPLOAD_DOCUMENTS) record_filing_path_source(draft, FilingPathSource.START_MENU) return redirect(get_step_url(WorkflowStepKey.UPLOAD_DOCUMENTS, jurisdiction)) - # No path named (or one we do not recognize): ask, rather than guess. - return redirect(get_step_url(WorkflowStepKey.FILING_PATH, jurisdiction)) + # Leave the answer unset until the filer confirms it beside their document. + record_filing_path_source(draft, FilingPathSource.START_MENU) + return redirect(get_step_url(WorkflowStepKey.UPLOAD_DOCUMENTS, jurisdiction)) @require_http_methods(["POST"]) diff --git a/efile_app/efile/views/extraction_review.py b/efile_app/efile/views/extraction_review.py index 7969b5be..b6ba2e0e 100644 --- a/efile_app/efile/views/extraction_review.py +++ b/efile_app/efile/views/extraction_review.py @@ -237,11 +237,11 @@ def extraction_review(request, jurisdiction): ) ) - offered_roles = ( - {role["id"] for role in _offered_filer_roles(request, jurisdiction)} - if existing_case == ExistingCase.NEW - else set() - ) + roles = _offered_filer_roles(request, jurisdiction) if existing_case == ExistingCase.NEW else [] + offered_roles = {role["id"] for role in roles} + # Confirm detected roles beside the suggestion. Without a suggestion, + # Check documents can ask later if its side-specific plan needs one. + role_confirmation_required = any(role["suggested"] for role in roles) or draft.filer_role in offered_roles filer_role = request.POST.get("filer_role", "") if availability_message: @@ -257,17 +257,16 @@ def extraction_review(request, jurisdiction): # a new case can't proceed on free-text guesses -- unlike an existing # case, which resolves these from the case lookup step instead. messages.error(request, "Choose a court, case category, and case type from the lists to continue.") - elif offered_roles and filer_role not in offered_roles: - # This case type means two different jobs, and the documents follow - # the side rather than the case, so there is nothing to show until - # the filer says which side is theirs. + elif (role_confirmation_required or filer_role) and filer_role not in offered_roles: + # A detected or previously confirmed role must be checked here. + # With no suggestion, the checklist can ask for the side later. messages.error(request, "Choose which side of this case you are on to continue.") elif approval_error := _document_approval_error(draft, request): # Last, so the files are only marked checked when the rest of the # form is accepted along with them. preview_error, status = approval_error else: - if offered_roles and draft.filer_role != filer_role: + if filer_role in offered_roles and draft.filer_role != filer_role: draft.filer_role = filer_role draft.save(update_fields=["filer_role", "updated_at"]) # Before the rest is saved: switching kinds of filing clears the @@ -360,8 +359,8 @@ def classified(level, key): "case_type_name": draft.case_type_name or classified("case type", "name"), "filing_type_code": (lead.filing_type_code if lead else "") or classified("filing type", "route_key"), "filing_type_name": (lead.filing_type_name if lead else "") or classified("filing type", "name"), - # Only some case types have sides. The screen asks for one as soon as - # the chosen case type turns out to be one of them. + # Keep a confirmed side visible; otherwise ask here only when the + # filing type suggests one. The checklist handles undecided sides. "filer_role": draft.filer_role, "zip_shortcuts": _zip_shortcuts(request, draft, jurisdiction), } From d929bc47fd93408225c1be6ea9b8110a07b96625 Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Sat, 17 Oct 2026 14:01:29 -0400 Subject: [PATCH 02/11] Update public page menu assertion for unified filing entry --- efile_app/efile/tests/test_public_pages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/efile_app/efile/tests/test_public_pages.py b/efile_app/efile/tests/test_public_pages.py index 9886fd1a..ddfcd78a 100644 --- a/efile_app/efile/tests/test_public_pages.py +++ b/efile_app/efile/tests/test_public_pages.py @@ -152,7 +152,7 @@ def test_about_page_carries_filing_menu_when_signed_in(client, django_user_model assert response.status_code == 200 content = response.content.decode() - assert "Start a new case" in content + assert "Start filing" in content assert "My draft e-filings" in content assert "Illinois e-Filing" in content From ae720b3642e3b63a7d786159ee022c51a9fc48ec Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Sat, 17 Oct 2026 14:42:17 -0400 Subject: [PATCH 03/11] Keep the side question consistent between Confirm and the checklist - Ask for a side on Confirm by the same rule in the browser and on the server: when one is suggested or the saved side is still on offer. - Clear a saved side the new case type no longer offers. - Send a Review detour with an unanswered side through Check documents. - Share needs_filer_role between Confirm and Check documents. - Simplify start_filing, the path radio lookups, and the AI remember toggle. Co-Authored-By: Claude Opus 5.5 --- efile_app/efile/services/filing_plans.py | 6 +++ .../efile/static/js/extraction-review.js | 40 +++++++++++-------- efile_app/efile/static/js/upload-documents.js | 14 +++---- efile_app/efile/tests/test_filer_role.py | 38 ++++++++++++++++++ efile_app/efile/views/document_checklist.py | 3 +- efile_app/efile/views/draft_views.py | 7 ++-- efile_app/efile/views/extraction_review.py | 13 +++++- 7 files changed, 89 insertions(+), 32 deletions(-) diff --git a/efile_app/efile/services/filing_plans.py b/efile_app/efile/services/filing_plans.py index e164a9ae..9cfe16be 100644 --- a/efile_app/efile/services/filing_plans.py +++ b/efile_app/efile/services/filing_plans.py @@ -164,6 +164,12 @@ def filer_roles_for_draft(draft: FilingDraft) -> list[dict[str, Any]]: ) +def needs_filer_role(draft: FilingDraft) -> bool: + """Whether this case has sides and the filer has not said which is theirs.""" + + return not draft.filer_role and bool(filer_roles_for_draft(draft)) + + def set_filer_role(draft: FilingDraft, filer_role: str) -> bool: """Record which side of the case the filer is on, if it is one on offer.""" diff --git a/efile_app/efile/static/js/extraction-review.js b/efile_app/efile/static/js/extraction-review.js index 1a61ada9..963b6c91 100644 --- a/efile_app/efile/static/js/extraction-review.js +++ b/efile_app/efile/static/js/extraction-review.js @@ -6,8 +6,13 @@ const context = JSON.parse(contextEl.textContent); const guesses = context.guesses || {}; + // "new", "existing", or "" before the filer has answered. + function chosenPath() { + return form.querySelector('input[name="existing_case"]:checked')?.value || ""; + } + function isExisting() { - return form.querySelector('input[name="existing_case"]:checked')?.value === "existing"; + return chosenPath() === "existing"; } const errorBox = document.getElementById("extraction-review-error"); const acknowledgement = document.getElementById("reviewed_extraction"); @@ -245,8 +250,7 @@ } function existingCaseWire() { - const checked = form.querySelector('input[name="existing_case"]:checked'); - return checked && checked.value === "existing" ? "yes" : "no"; + return isExisting() ? "yes" : "no"; } const LISTS = { @@ -391,7 +395,10 @@ const roleField = document.getElementById("filer-role-field"); const roleOptions = document.getElementById("filer-role-options"); - let savedRole = context.filer_role || ""; + // The side saved on the draft. The server asks for it again whenever the + // chosen case type still offers it, so the screen must show it then too. + const draftRole = context.filer_role || ""; + let savedRole = draftRole; let roleGeneration = 0; function chosenRole() { @@ -478,14 +485,14 @@ // -- an eviction is two different filings depending on who is making it -- // and which ones depends on the case type chosen above, so the question // appears and disappears with it. - function roleOptionHtml(role) { - const hint = role.suggested && !savedRole ? + function roleOptionHtml(role, chosen) { + const hint = role.suggested && !chosen ? ` ${gettext("probably you, from the document you uploaded")}` : ""; const description = role.description ? `${escapeHtml(role.description)}` : ""; return ` `; } @@ -517,9 +524,11 @@ console.warn("Could not load the sides of this case:", error); } if (token !== roleGeneration) return; - if (!roles.some((role) => role.id === savedRole)) savedRole = ""; - const needsConfirmation = Boolean(savedRole) || roles.some((role) => role.suggested); - roleOptions.innerHTML = needsConfirmation ? roles.map(roleOptionHtml).join("") : ""; + // Same rule as the server: ask when a side is suggested or the saved + // side is on offer. An answer given here is kept while it is offered. + const chosen = roles.some((role) => role.id === savedRole) ? savedRole : ""; + const needsConfirmation = Boolean(chosen) || roles.some((role) => role.suggested || role.id === draftRole); + roleOptions.innerHTML = needsConfirmation ? roles.map((role) => roleOptionHtml(role, chosen)).join("") : ""; roleField.hidden = !needsConfirmation; } @@ -667,7 +676,7 @@ proposal.disabled = !existing; } const details = document.getElementById("new-case-details"); - const isNew = form.querySelector('input[name="existing_case"]:checked')?.value === "new"; + const isNew = chosenPath() === "new"; details.hidden = !isNew; details.disabled = !isNew; const parties = document.getElementById("review-parties"); @@ -713,18 +722,15 @@ form.addEventListener("submit", (event) => { if (isExisting()) return; - if (!form.querySelector('input[name="existing_case"]:checked')) { + if (!chosenPath()) { event.preventDefault(); errorBox.textContent = gettext("Choose whether this is a new or existing court case to continue."); errorBox.hidden = false; - document.getElementById("path-question").scrollIntoView({ - block: "center" - }); + openPathQuestion(""); return; } if (blockUnfinished(event)) return; - const isNew = form.querySelector('input[name="existing_case"]:checked')?.value === "new"; - const missingCase = isNew && (!fields.court.select.value || !fields.case_category.select.value || !fields.case_type.select.value); + const missingCase = !fields.court.select.value || !fields.case_category.select.value || !fields.case_type.select.value; const missingRole = !roleField.hidden && !chosenRole(); if (!missingCase && !missingRole) return; diff --git a/efile_app/efile/static/js/upload-documents.js b/efile_app/efile/static/js/upload-documents.js index 26b04cda..64b1a0aa 100644 --- a/efile_app/efile/static/js/upload-documents.js +++ b/efile_app/efile/static/js/upload-documents.js @@ -92,7 +92,7 @@ } // Merely opening preferences never changes the account default. Include // it only after the filer changes an AI setting during this page load. - let rememberOffered = false; + let aiChoiceTouched = false; function aiIsOff() { return Boolean(aiOptOut && aiOptOut.checked); @@ -118,18 +118,13 @@ }); } - function offerToRemember() { - if (!aiRemember || rememberOffered) return; - rememberOffered = true; - aiRemember.open = true; - } async function saveAiPreference() { const optedOut = aiOptOut.checked; const body = new FormData(); body.append("action", "ai_preference"); body.append("ai_opt_out", optedOut ? "yes" : ""); - if (rememberOffered && aiRememberChoice) { + if (aiChoiceTouched && aiRememberChoice) { body.append("remember_ai_choice", aiRememberChoice.checked ? "yes" : "no"); } body.append("csrfmiddlewaretoken", apiUtils.getCSRFToken()); @@ -155,13 +150,14 @@ const optedOut = aiOptOut.checked; if (aiNoteOn) aiNoteOn.hidden = optedOut; if (aiNoteOff) aiNoteOff.hidden = !optedOut; - offerToRemember(); + aiChoiceTouched = true; + if (aiRemember) aiRemember.open = true; saveAiPreference(); }); } if (aiRememberChoice) aiRememberChoice.addEventListener("change", () => { - rememberOffered = true; + aiChoiceTouched = true; saveAiPreference(); }); diff --git a/efile_app/efile/tests/test_filer_role.py b/efile_app/efile/tests/test_filer_role.py index face26b9..5137c171 100644 --- a/efile_app/efile/tests/test_filer_role.py +++ b/efile_app/efile/tests/test_filer_role.py @@ -324,3 +324,41 @@ def test_deferred_side_cannot_accept_an_unoffered_role(client, signed_in): assert response.status_code == 200 signed_in.refresh_from_db() assert signed_in.filer_role == "" + + +@pytest.mark.django_db +def test_a_saved_side_still_on_offer_must_be_confirmed(client, signed_in): + signed_in.filer_role = "landlord" + signed_in.save() + + response = confirm_filing(client, filing_type_name="Other filing") + + assert response.status_code == 200 + assert "Choose which side of this case you are on" in response.content.decode() + signed_in.refresh_from_db() + assert signed_in.filer_role == "landlord" + + +@pytest.mark.django_db +def test_a_side_the_new_case_type_does_not_offer_is_cleared(client, signed_in): + signed_in.filer_role = "petitioner" + signed_in.save() + + response = confirm_filing(client, filing_type_name="Other filing") + + assert response.status_code == 302 + signed_in.refresh_from_db() + assert signed_in.filer_role == "" + assert client.get(response.url).context["choosing_filer_role"] is True + + +@pytest.mark.django_db +def test_a_review_detour_with_an_unanswered_side_goes_through_the_checklist(client, signed_in): + response = confirm_filing(client, filing_type_name="Other filing", return_to="review") + + assert response.status_code == 302 + path, _, query = response.url.partition("?") + assert path == CHECKLIST_URL + assert "return_to=review" in query + signed_in.refresh_from_db() + assert signed_in.current_step == WorkflowStepKey.DOCUMENT_CHECKLIST diff --git a/efile_app/efile/views/document_checklist.py b/efile_app/efile/views/document_checklist.py index 580774b1..c3f71f74 100644 --- a/efile_app/efile/views/document_checklist.py +++ b/efile_app/efile/views/document_checklist.py @@ -25,6 +25,7 @@ filing_type_for_item, grouped_checklist, mark_item_have, + needs_filer_role, set_checklist_answers, set_filer_role, status_choices, @@ -217,7 +218,7 @@ def document_checklist(request, jurisdiction): # Normally answered on the confirm-filing step. It can still be # unanswered here on the existing-case path, where the case type is not # known until after the case lookup, so the question has a home here too. - "choosing_filer_role": bool(filer_roles) and not draft.filer_role, + "choosing_filer_role": needs_filer_role(draft), "checklist_groups": grouped_checklist(plan, draft), "status_choices": status_choices(), "guidance": plan.guidance if plan else {}, diff --git a/efile_app/efile/views/draft_views.py b/efile_app/efile/views/draft_views.py index 6d55bc96..324599ed 100644 --- a/efile_app/efile/views/draft_views.py +++ b/efile_app/efile/views/draft_views.py @@ -38,11 +38,10 @@ def start_filing(request, jurisdiction): ) logger.info("Started draft id=%s from the navigation menu (path=%s)", draft.pk, path or "unset") + # Without an answer, leave it unset until the filer confirms it beside + # their document. if path in {ExistingCase.NEW, ExistingCase.EXISTING}: - write_case_data(draft, {"existing_case": path}, current_step=WorkflowStepKey.UPLOAD_DOCUMENTS) - record_filing_path_source(draft, FilingPathSource.START_MENU) - return redirect(get_step_url(WorkflowStepKey.UPLOAD_DOCUMENTS, jurisdiction)) - # Leave the answer unset until the filer confirms it beside their document. + write_case_data(draft, {"existing_case": path}) record_filing_path_source(draft, FilingPathSource.START_MENU) return redirect(get_step_url(WorkflowStepKey.UPLOAD_DOCUMENTS, jurisdiction)) diff --git a/efile_app/efile/views/extraction_review.py b/efile_app/efile/views/extraction_review.py index b6ba2e0e..ebf5b451 100644 --- a/efile_app/efile/views/extraction_review.py +++ b/efile_app/efile/views/extraction_review.py @@ -31,6 +31,7 @@ describe_path_change, filing_path_conflict, ) +from efile.services.filing_plans import needs_filer_role from efile.services.filing_type_proposals import current_proposal, save_proposal from efile.utils.s3_upload_handler import S3UploadHandler from efile.workflow import ( @@ -266,7 +267,10 @@ def extraction_review(request, jurisdiction): # form is accepted along with them. preview_error, status = approval_error else: - if filer_role in offered_roles and draft.filer_role != filer_role: + # Validated above, so this is an offered side or, when nothing + # needed confirming, a blank that drops a side the new case type + # no longer has. + if existing_case == ExistingCase.NEW and draft.filer_role != filer_role: draft.filer_role = filer_role draft.save(update_fields=["filer_role", "updated_at"]) # Before the rest is saved: switching kinds of filing clears the @@ -324,6 +328,13 @@ def extraction_review(request, jurisdiction): if existing_case == ExistingCase.EXISTING and not draft.previous_case_id: write_case_data(draft, {}, current_step=WorkflowStepKey.CASE_LOOKUP) return redirect(with_return_to(get_step_url(WorkflowStepKey.CASE_LOOKUP, jurisdiction), return_to)) + if needs_filer_role(draft): + # The side was left for the checklist to ask, so the + # detour goes there before returning. + write_case_data(draft, {}, current_step=WorkflowStepKey.DOCUMENT_CHECKLIST) + return redirect( + with_return_to(get_step_url(WorkflowStepKey.DOCUMENT_CHECKLIST, jurisdiction), return_to) + ) linear = get_next_step(WorkflowStepKey.EXTRACTION_REVIEW, draft) default_step = linear.key if linear else WorkflowStepKey.REVIEW write_case_data(draft, {}, current_step=continue_step(draft, return_to, default_step)) From 41c2f277febfae8f7729b3caeef5648093932caf Mon Sep 17 00:00:00 2001 From: Quinten Steenhuis Date: Sat, 17 Oct 2026 15:55:36 -0400 Subject: [PATCH 04/11] Address remaining review findings on upload, options and party removal - Closing "Replace a document" resets the hidden replace choice, so a new file is added instead of silently overwriting one. - Rename the My filings heading id to match its content. - Test removing a party from its Edit screen, keeping the Review detour. Co-Authored-By: Claude Opus 5.5 --- efile_app/efile/static/js/upload-documents.js | 8 +++++ efile_app/efile/templates/efile/options.html | 4 +-- .../templates/efile/upload_documents.html | 2 +- efile_app/efile/tests/test_people_flow.py | 36 +++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/efile_app/efile/static/js/upload-documents.js b/efile_app/efile/static/js/upload-documents.js index 64b1a0aa..9efeac65 100644 --- a/efile_app/efile/static/js/upload-documents.js +++ b/efile_app/efile/static/js/upload-documents.js @@ -156,6 +156,14 @@ }); } + // Closing "Replace a document" goes back to adding: a Replace choice the + // filer can no longer see must not overwrite one of their documents. + const replaceDocument = document.getElementById("replace-document"); + const replaceDisclosure = document.getElementById("replace-document-disclosure"); + if (replaceDocument && replaceDisclosure) replaceDisclosure.addEventListener("toggle", () => { + if (!replaceDisclosure.open) replaceDocument.value = ""; + }); + if (aiRememberChoice) aiRememberChoice.addEventListener("change", () => { aiChoiceTouched = true; saveAiPreference(); diff --git a/efile_app/efile/templates/efile/options.html b/efile_app/efile/templates/efile/options.html index 2e31641f..b1220891 100644 --- a/efile_app/efile/templates/efile/options.html +++ b/efile_app/efile/templates/efile/options.html @@ -162,13 +162,13 @@

{% translate "Start a new filing" %}

-
+
-

{% translate "My filings" %}

+

{% translate "My filings" %}

{% translate "See the status of all documents you have e-filed with the court." %}

diff --git a/efile_app/efile/templates/efile/upload_documents.html b/efile_app/efile/templates/efile/upload_documents.html index b4249370..40c86f17 100644 --- a/efile_app/efile/templates/efile/upload_documents.html +++ b/efile_app/efile/templates/efile/upload_documents.html @@ -48,7 +48,7 @@

{% translate "Upload your documents" %}

data-wait-seconds="{{ extraction_wait_seconds }}"> {% csrf_token %} {% if documents %} -
+
{% translate "Replace a document" %} {% endfor %} - {% else %} + {% if documents|length == 1 %} + {# Nothing to choose: the document's card already says it is the main document. #} + {% for document in documents %}{% endfor %} + {% else %} +
+ {% ui_text "organize_documents.main_document_question" %}

{% ui_text "organize_documents.main_document_help" %}

{% for document in documents %} @@ -46,8 +46,8 @@

{% translate "Organize your documents" %}

{% endfor %}
- {% endif %} -
+ + {% endif %}
{% for document in documents %}
{% translate "Organize your documents" %} data-optional-services="{{ document.requested_optional_services|join:',' }}">
-
- - {% if document.role == "lead" %} - {% translate "Main document" %} - {% else %} + {{ document.name|default:document.original_filename }} +
+ {% if document.role == "lead" %} + {% translate "Main document" %} + {% else %} + {% blocktranslate with number=forloop.counter0 %}Additional document {{ number }}{% endblocktranslate %} - {% endif %} - - {{ document.name|default:document.original_filename }} + + {% endif %} + {% if document.role == "supporting" %} +
+ + +
+ {% endif %}
- {% if document.role == "supporting" %} -
- - -
- {% else %} - {% translate "Main document" %} - {% endif %}
Date: Mon, 19 Oct 2026 17:12:37 -0400 Subject: [PATCH 11/11] Drop the state note when only accepted locations are listed The dropdown already limits the choice to locations the filing service accepts, and an address saved outside that list gets an error that says what to do. Keep the notes for the fallback lists. Co-Authored-By: Claude Opus 5.5 --- .../efile/partials/state_choices.html | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/efile_app/efile/templates/efile/partials/state_choices.html b/efile_app/efile/templates/efile/partials/state_choices.html index 0b77852d..140cbde0 100644 --- a/efile_app/efile/templates/efile/partials/state_choices.html +++ b/efile_app/efile/templates/efile/partials/state_choices.html @@ -10,19 +10,24 @@ {% endif %} - - {% if state_source == "efsp" %} - {% translate "Only locations accepted by the filing service are listed. If your address is not listed, contact the court for filing instructions. Do not choose a different state." %} - {% elif state_source == "usps" %} - {% translate "The filing service's location list is unavailable. USPS locations, including territories and military addresses, are shown. The filing service will check whether it accepts your address." %} - {% else %} - {% translate "The filing service's list of states or provinces is unavailable. Enter your address as written; the filing service will check whether it accepts it." %} - {% endif %} - +{% comment %} +The filing service's own list needs no note: only accepted locations can be +chosen, and an address saved outside it gets an error that says what to do. +The fallbacks explain why the list looks different. +{% endcomment %} +{% if state_source != "efsp" %} + + {% if state_source == "usps" %} + {% translate "The filing service's location list is unavailable. USPS locations, including territories and military addresses, are shown. The filing service will check whether it accepts your address." %} + {% else %} + {% translate "The filing service's list of states or provinces is unavailable. Enter your address as written; the filing service will check whether it accepts it." %} + {% endif %} + +{% endif %}