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/docs/docs/partners-courts/customization.md b/docs/docs/partners-courts/customization.md index d10eae4a..5fa46774 100644 --- a/docs/docs/partners-courts/customization.md +++ b/docs/docs/partners-courts/customization.md @@ -66,7 +66,6 @@ putting `text` under a court does not create a court-specific override. | `terms.main_document` | main document | No | | `organize_documents.main_document_question` | Which PDF is the `{main_document}`? | No | | `organize_documents.main_document_help` | Choose the document that starts this filing, like a `{starting_document_example}`. | Yes | -| `organize_documents.single_document_note` | This is your `{main_document}`. | No | | `organize_documents.filing_component_label` | Court filing component | No | | `organize_documents.filing_component_help` | The court may call more than one PDF a “Lead Document”. This is separate from your `{main_document}`. | Yes | | `organize_documents.filing_component_fixed_note` | Required by the court. | No | diff --git a/efile_app/efile/services/document_preparation.py b/efile_app/efile/services/document_preparation.py index 6ae1cc1c..756d09fa 100644 --- a/efile_app/efile/services/document_preparation.py +++ b/efile_app/efile/services/document_preparation.py @@ -13,7 +13,7 @@ from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from pypdf import PdfReader, PdfWriter -from pypdf.generic import ArrayObject, DictionaryObject +from pypdf.generic import ArrayObject, DictionaryObject, NameObject from efile.utils.config_loader import config_loader @@ -163,6 +163,51 @@ def _repair_text_appearances(content, source, widgets): ) from error +def _blank_and_undrawn(widget): + """True for a widget with no appearance and no answer, such as an unsigned signature line. + + A missing appearance alone is not enough: a checked box or chosen option + can lose its appearance and still hold an answer, and that must not be + dropped quietly. + """ + appearance = widget.get("/AP") + if appearance and appearance.get_object().get("/N"): + return False + field = widget.get("/Parent", widget).get_object() + value = widget.get("/V", field.get("/V")) + return value is None or str(value) in {"", "/Off"} + + +def _drop_undrawn_widgets(content, reader, widgets): + """Remove leftover widgets that are blank and have no appearance. + + The flatten engine skips widgets with nothing to draw, so they survive as + form fields. They print as nothing, so removing them changes no page. + """ + if not widgets or not all(_blank_and_undrawn(widget) for widget in widgets): + return content + writer = PdfWriter(clone_from=reader) + for page in writer.pages: + if "/Annots" not in page: + continue + annotations = page["/Annots"].get_object() + if not isinstance(annotations, ArrayObject): + continue + kept = [ + ref + for ref in annotations + if not (ref.get_object().get("/Subtype") == "/Widget" and _blank_and_undrawn(ref.get_object())) + ] + if kept: + page[NameObject("/Annots")] = ArrayObject(kept) + else: + del page["/Annots"] + writer._root_object.pop("/AcroForm", None) + output = io.BytesIO() + writer.write(output) + return output.getvalue() + + def _flatten(content): source, widgets = inspect_pdf(content) if not widgets: @@ -172,6 +217,7 @@ def _flatten(content): raise PreparationError("We cannot prepare this signed PDF. Print it to PDF and upload that copy.") content = _repair_text_appearances(content, source, widgets) result = _gotenberg(content, ".pdf", "/forms/pdfengines/flatten") + result = _drop_undrawn_widgets(result, *inspect_pdf(result)) output, remaining = inspect_pdf(result) if remaining or output.get_fields() or len(output.pages) != len(source.pages): raise PreparationError("We could not prepare this form. Print it to PDF and upload that copy.") 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/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/css/components/filing-code-search.css b/efile_app/efile/static/css/components/filing-code-search.css index 8ad5b480..511fcdcc 100644 --- a/efile_app/efile/static/css/components/filing-code-search.css +++ b/efile_app/efile/static/css/components/filing-code-search.css @@ -1,10 +1,26 @@ .code-search-invitation { - margin-block: 1.5rem; - padding: 1.25rem; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 0.75rem 1.5rem; + margin-top: 1rem; + padding: 1rem 1.25rem; background: #edf5ff; + border: 1px solid #cbdcf1; border-radius: 0.75rem; } +.code-search-invitation__text { + display: flex; + flex-direction: column; + gap: 0.15rem; +} + +.code-search-invitation__text [hidden] { + display: none; +} + .code-search { width: min(48rem, calc(100% - 2rem)); height: min(60rem, calc(100dvh - 2rem)); diff --git a/efile_app/efile/static/css/reorganized-flow.css b/efile_app/efile/static/css/reorganized-flow.css index 6de0f947..002b84a1 100644 --- a/efile_app/efile/static/css/reorganized-flow.css +++ b/efile_app/efile/static/css/reorganized-flow.css @@ -209,6 +209,7 @@ justify-content: center; min-height: 190px; padding: 2rem; + position: relative; text-align: center; } @@ -432,6 +433,11 @@ color: var(--better-blue); } +.status-pill--additional { + background: var(--border-subtle); + color: var(--text-muted); +} + .document-list__empty { border: 1px solid var(--border-default); border-radius: 10px; @@ -1097,14 +1103,21 @@ padding: 1rem; } +/* Floated so the legend sits inside the box instead of on its border. */ .main-document-choice legend, .primary-question legend { color: var(--text-heading); - float: none; + float: left; font-size: 1.05rem; font-weight: 750; margin: 0; - width: auto; + padding: 0; + width: 100%; +} + +.main-document-choice legend+*, +.primary-question legend+* { + clear: both; } .main-document-choice>p, @@ -1139,17 +1152,21 @@ font-size: 1.4rem; } -.organize-card__header strong, -.organize-card__position { +.organize-card__header strong { display: block; } +/* The document's place in the filing, with any reorder buttons beside it. */ +.organize-card__meta { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + justify-content: flex-end; +} + .organize-card__position { - color: var(--text-muted); - font-size: 0.78rem; - font-weight: 700; - letter-spacing: 0.03em; - text-transform: uppercase; + white-space: nowrap; } .organize-card__fields { @@ -1207,6 +1224,12 @@ width: auto; } +/* Each choice explains itself between its question and its options, so the + explanations line up across the two columns. */ +.compact-choice-field .form-help { + margin: 0 0 0.6rem; +} + .form-help { color: var(--text-muted); display: block; @@ -1576,6 +1599,15 @@ grid-template-columns: 1fr; } + .organize-card__header { + grid-template-columns: auto 1fr; + } + + .organize-card__meta { + grid-column: 1 / -1; + justify-content: space-between; + } + .people-grid, .people-grid--name, .your-role-card { @@ -1996,6 +2028,49 @@ margin: 0; } +/* The court, case and filing type, set apart as one task with two routes: + the guided helper, or choosing each field directly. */ +.new-case-details { + background: var(--surface-subtle); + border: 1px solid var(--border-default); + border-radius: 12px; + margin-top: 1.5rem; + padding: 1.25rem; +} + +.new-case-details__or { + align-items: center; + color: var(--text-muted); + display: flex; + font-size: 0.92rem; + gap: 0.75rem; + margin: 1rem 0; +} + +.new-case-details__or::before, +.new-case-details__or::after { + border-top: 1px solid var(--border-default); + content: ""; + flex: 1; +} + +/* Floated so the legend sits inside the card instead of on its border. */ +.new-case-details__heading { + color: var(--text-heading); + float: left; + font-size: 1.05rem; + font-weight: 750; + margin-bottom: 0.4rem; + padding: 0; + width: 100%; +} + +.new-case-details__lede { + clear: both; + color: var(--text-muted); + margin-bottom: 0; +} + /* New or existing case, first among the filing details. */ .path-section { margin-bottom: 1.5rem; @@ -2030,4 +2105,12 @@ .drop-zone label { cursor: pointer; width: 100%; +} + +/* Stretch the label over the whole drop zone so a click anywhere in it opens + the file picker, while the label text stays the input's accessible name. */ +.drop-zone label::after { + content: ""; + inset: 0; + position: absolute; } \ No newline at end of file diff --git a/efile_app/efile/static/js/extraction-review.js b/efile_app/efile/static/js/extraction-review.js index 22312ccc..31ea8f7c 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"); @@ -128,6 +133,16 @@ field.editButton?.addEventListener("click", () => beginEdit(key)); }); + // Once the page has filled in a choice, the helper's prompt asks the filer + // to check it rather than offering to start from nothing. + function showPrefilledPrompt() { + const prompt = document.getElementById("code-search-prompt"); + const prefilled = document.getElementById("code-search-prompt-prefilled"); + if (!prompt || !prefilled) return; + prompt.hidden = true; + prefilled.hidden = false; + } + function setMode(key, mode) { const field = fields[key]; field.mode = mode; @@ -245,8 +260,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 = { @@ -352,7 +366,10 @@ field.hint.textContent = field.defaultHint; // Only the page's first look folds a match into its summary. After // that a field stays the way the filer left it. - if (firstLoad) setMode(key, "found"); + if (firstLoad) { + setMode(key, "found"); + showPrefilledPrompt(); + } } else if (wanted && !firstLoad) { // The filer's choice is not on the new list. Said beside the field, // naming what went, rather than left to look like it never existed. @@ -391,7 +408,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() { @@ -441,6 +461,7 @@ return false; } courtSelector = selector; + if (field.select.value) showPrefilledPrompt(); // The selector shows the court it settled on, and folds and reopens // its own questions with their own Update and Cancel, so the field's // found/edit panels have nothing left to say. @@ -478,14 +499,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,8 +538,12 @@ 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; + // 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; } const ADVANCE = { @@ -665,8 +690,9 @@ proposal.disabled = !existing; } const details = document.getElementById("new-case-details"); - details.hidden = existing; - details.disabled = existing; + const isNew = chosenPath() === "new"; + details.hidden = !isNew; + details.disabled = !isNew; const parties = document.getElementById("review-parties"); if (parties) { parties.hidden = existing; @@ -676,8 +702,14 @@ availability.check(); } + const PATH_ERROR = gettext("Choose whether this is a new or existing court case to continue."); + form.querySelectorAll('input[name="existing_case"]').forEach((radio) => { - radio.addEventListener("change", updateCasePathSections); + radio.addEventListener("change", () => { + // Answering the question clears the error that asked for it. + if (errorBox.textContent === PATH_ERROR) errorBox.hidden = true; + updateCasePathSections(); + }); }); updateCasePathSections(); @@ -710,9 +742,15 @@ form.addEventListener("submit", (event) => { if (isExisting()) return; + if (!chosenPath()) { + event.preventDefault(); + errorBox.textContent = PATH_ERROR; + errorBox.hidden = false; + 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/filing-code-search.js b/efile_app/efile/static/js/filing-code-search.js index 83e83b4f..f477ab92 100644 --- a/efile_app/efile/static/js/filing-code-search.js +++ b/efile_app/efile/static/js/filing-code-search.js @@ -176,18 +176,18 @@ } }).catch(error => { if (error.name === "AbortError") throw error; - const unavailable = new Error(gettext("Code search could not be loaded. Try again shortly.")); + const unavailable = new Error(gettext("This helper could not be loaded. Try again shortly.")); unavailable.retryable = true; throw unavailable; }); if (!response.headers.get("content-type")?.includes("application/json")) { - const error = new Error(gettext("Code search could not be loaded. Refresh the page and try again.")); + const error = new Error(gettext("This helper could not be loaded. Refresh the page and try again.")); error.retryable = response.status >= 500; throw error; } const data = await response.json(); if (!response.ok) { - const error = new Error(data.error || gettext("Code search could not be loaded. Try again.")); + const error = new Error(data.error || gettext("This helper could not be loaded. Try again.")); error.retryable = response.status === 503; throw error; } diff --git a/efile_app/efile/static/js/upload-documents.js b/efile_app/efile/static/js/upload-documents.js index 3ee7d1b8..9efeac65 100644 --- a/efile_app/efile/static/js/upload-documents.js +++ b/efile_app/efile/static/js/upload-documents.js @@ -90,10 +90,9 @@ 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. - let rememberOffered = false; + // Merely opening preferences never changes the account default. Include + // it only after the filer changes an AI setting during this page load. + let aiChoiceTouched = false; function aiIsOff() { return Boolean(aiOptOut && aiOptOut.checked); @@ -119,21 +118,13 @@ }); } - function offerToRemember() { - if (!aiRemember || rememberOffered) return; - rememberOffered = true; - if (window.bootstrap) new window.bootstrap.Collapse(aiRemember, { - toggle: false - }).show(); - else aiRemember.classList.add("show"); - } 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()); @@ -159,12 +150,24 @@ 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", saveAiPreference); + // 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(); + }); function syncFiles() { const transfer = new DataTransfer(); diff --git a/efile_app/efile/templates/efile/components/filing_code_search.html b/efile_app/efile/templates/efile/components/filing_code_search.html index 8b08be6b..af779552 100644 --- a/efile_app/efile/templates/efile/components/filing_code_search.html +++ b/efile_app/efile/templates/efile/components/filing_code_search.html @@ -1,10 +1,18 @@ {% load i18n %}