Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions docs/developer-notes/miro-vision-alignment-2026-10-17.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/docs/partners-courts/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
48 changes: 47 additions & 1 deletion efile_app/efile/services/document_preparation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand All @@ -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.")
Expand Down
14 changes: 6 additions & 8 deletions efile_app/efile/services/filing_path.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions efile_app/efile/services/filing_plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
20 changes: 18 additions & 2 deletions efile_app/efile/static/css/components/filing-code-search.css
Original file line number Diff line number Diff line change
@@ -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));
Expand Down
101 changes: 92 additions & 9 deletions efile_app/efile/static/css/reorganized-flow.css
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
justify-content: center;
min-height: 190px;
padding: 2rem;
position: relative;
text-align: center;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Loading
Loading