ReportReviewScreen.kt / ReportReviewViewModel.kt today collect an optional, unvalidated comment (OutlinedTextField, label "What went wrong? (optional)") and submit unconditionally
(the Submit button is disabled only while SubmitUiState.SUBMITTING). There is no minimum length,
no content-quality check, and no email field at all — DebugReport (reporting/DebugReport.kt)
has no field to carry one. This ticket adds friction against low-effort submissions and collects a
reply-to address.
Scope
Minimum length, enforced: require 200 characters in the comment field before Submit is
enabled (mirror the existing enabled = state.submit != SubmitUiState.SUBMITTING check in ReportReviewScreen.kt with an added length condition, backed by new validation state in ReportReviewViewModel).
Live counter: show "x/200" below the text field, updating as the user types.
Invalid-submit feedback: if the user taps Submit while under the threshold (relevant once
the button itself is only disabled, some users tap disabled controls or arrive via
accessibility tools) — or, if the button stays enabled and this is meant as inline validation
instead, tapping Submit under-length turns the counter text and the field's outline red.
Resolve which of these two interaction models is intended before implementing (the ticket's
wording suggests the button both stays non-functional under threshold and gives red
error feedback on an attempted tap — clarify with the reporter if ambiguous).
English-only note: a short static string near the comment field noting the maintainer
only reads English.
Low-effort content heuristic: reject comments that are nonsensically repeating characters,
repeated whitespace runs, or otherwise don't resemble real words/language. This needs to stay
a cheap heuristic (e.g. longest repeated-character run, repeated-whitespace run, ratio of
distinct "word" tokens to total length) — not a language model or dictionary lookup. Define
the exact thresholds during implementation and unit-test them against both false positives
(real short complaints) and the obvious abuse cases ("aaaaaaa...", "asdf asdf asdf",
etc.). On failure, show exactly: "Please ensure your message is detailed enough for
submission." — deliberately generic, so the heuristic itself isn't disclosed/game-able.
Password-mention escalation: if the comment contains the word "password" or a
1-character-edit variant of it (insertion/deletion/substitution/transposition — i.e.
Levenshtein/Damerau-Levenshtein distance 1), raise the required length for that submission
from 200 to 2000 characters. Define this as a small pure function so it's unit-testable in
isolation (ContactPermissionDecision-style, see contacts/ContactPermissionDecision.kt for
the pattern this repo already uses for this kind of pure decision logic).
Required email field: add an email OutlinedTextField (standard email-format
validation — e.g. android.util.Patterns.EMAIL_ADDRESS), required for Submit to be enabled.
This needs a new field end-to-end: ReportReviewState.email, a setter on the view model, and
a new field on DebugReport (reporting/DebugReport.kt) so it round-trips through toSubmissionPayload()/toStorageJson()/fromStorageJson() — it's a flat JSON DTO with no
Room migration involved, so this is a straightforward addition there.
Password-support notice: add a static notice on the review/submit screen: "LibreMail
does not help with lost/forgotten password issues." (Pairs with #161's confirmation copy,
which reiterates that submissions aren't guaranteed a response.)
Acceptance criteria
Submit cannot succeed with fewer than 200 characters (2000 if a password-like word appears), a
missing/invalid email, or content that fails the low-effort heuristic.
The counter, red-state feedback, English-only note, and password-support notice are all visible
on the review screen.
JVM unit tests cover the length thresholds, the password 1-edit-distance detection, and the
low-effort heuristic's true/false positives.
The new required email field is PII collected specifically for a user-initiated submission (data
the user is choosing to hand over for a reply, distinct from the report's own best-effort
anonymization). If/when the Cloudflare ingest pipeline (#33/#34) lands, make sure its
PII-anonymization pass (#34) doesn't strip or clash with this intentionally supplied contact
address, and that its handling is covered by the same privacy documentation referenced there.
Pairs with #161 (submission confirmation experience).
## Context
`ReportReviewScreen.kt` / `ReportReviewViewModel.kt` today collect an optional, unvalidated
`comment` (`OutlinedTextField`, label "What went wrong? (optional)") and submit unconditionally
(the Submit button is disabled only while `SubmitUiState.SUBMITTING`). There is no minimum length,
no content-quality check, and no email field at all — `DebugReport` (`reporting/DebugReport.kt`)
has no field to carry one. This ticket adds friction against low-effort submissions and collects a
reply-to address.
## Scope
- [ ] **Minimum length, enforced:** require 200 characters in the comment field before Submit is
enabled (mirror the existing `enabled = state.submit != SubmitUiState.SUBMITTING` check in
`ReportReviewScreen.kt` with an added length condition, backed by new validation state in
`ReportReviewViewModel`).
- [ ] **Live counter:** show "x/200" below the text field, updating as the user types.
- [ ] **Invalid-submit feedback:** if the user taps Submit while under the threshold (relevant once
the button itself is only *disabled*, some users tap disabled controls or arrive via
accessibility tools) — or, if the button stays enabled and this is meant as inline validation
instead, tapping Submit under-length turns the counter text and the field's outline red.
Resolve which of these two interaction models is intended before implementing (the ticket's
wording suggests the button *both* stays non-functional under threshold *and* gives red
error feedback on an attempted tap — clarify with the reporter if ambiguous).
- [ ] **English-only note:** a short static string near the comment field noting the maintainer
only reads English.
- [ ] **Low-effort content heuristic:** reject comments that are nonsensically repeating characters,
repeated whitespace runs, or otherwise don't resemble real words/language. This needs to stay
a cheap heuristic (e.g. longest repeated-character run, repeated-whitespace run, ratio of
distinct "word" tokens to total length) — not a language model or dictionary lookup. Define
the exact thresholds during implementation and unit-test them against both false positives
(real short complaints) and the obvious abuse cases (`"aaaaaaa...`", `"asdf asdf asdf"`,
etc.). On failure, show exactly: "Please ensure your message is detailed enough for
submission." — deliberately generic, so the heuristic itself isn't disclosed/game-able.
- [ ] **Password-mention escalation:** if the comment contains the word "password" or a
1-character-edit variant of it (insertion/deletion/substitution/transposition — i.e.
Levenshtein/Damerau-Levenshtein distance 1), raise the required length for *that* submission
from 200 to 2000 characters. Define this as a small pure function so it's unit-testable in
isolation (`ContactPermissionDecision`-style, see `contacts/ContactPermissionDecision.kt` for
the pattern this repo already uses for this kind of pure decision logic).
- [ ] **Required email field:** add an email `OutlinedTextField` (standard email-format
validation — e.g. `android.util.Patterns.EMAIL_ADDRESS`), required for Submit to be enabled.
This needs a new field end-to-end: `ReportReviewState.email`, a setter on the view model, and
a new field on `DebugReport` (`reporting/DebugReport.kt`) so it round-trips through
`toSubmissionPayload()`/`toStorageJson()`/`fromStorageJson()` — it's a flat JSON DTO with no
Room migration involved, so this is a straightforward addition there.
- [ ] **Password-support notice:** add a static notice on the review/submit screen: "LibreMail
does not help with lost/forgotten password issues." (Pairs with #161's confirmation copy,
which reiterates that submissions aren't guaranteed a response.)
## Acceptance criteria
- Submit cannot succeed with fewer than 200 characters (2000 if a password-like word appears), a
missing/invalid email, or content that fails the low-effort heuristic.
- The counter, red-state feedback, English-only note, and password-support notice are all visible
on the review screen.
- JVM unit tests cover the length thresholds, the password 1-edit-distance detection, and the
low-effort heuristic's true/false positives.
## Relevant files
- `ui/reporting/ReportReviewScreen.kt`, `ui/reporting/ReportReviewViewModel.kt`,
`reporting/DebugReport.kt`, `res/values/strings.xml` (`report_*` block).
## Dependencies / notes
- The new required email field is PII collected specifically for a user-initiated submission (data
the user is choosing to hand over for a reply, distinct from the report's own best-effort
anonymization). If/when the Cloudflare ingest pipeline (#33/#34) lands, make sure its
PII-anonymization pass (#34) doesn't strip or clash with this *intentionally supplied* contact
address, and that its handling is covered by the same privacy documentation referenced there.
- Pairs with #161 (submission confirmation experience).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Context
ReportReviewScreen.kt/ReportReviewViewModel.kttoday collect an optional, unvalidatedcomment(OutlinedTextField, label "What went wrong? (optional)") and submit unconditionally(the Submit button is disabled only while
SubmitUiState.SUBMITTING). There is no minimum length,no content-quality check, and no email field at all —
DebugReport(reporting/DebugReport.kt)has no field to carry one. This ticket adds friction against low-effort submissions and collects a
reply-to address.
Scope
enabled (mirror the existing
enabled = state.submit != SubmitUiState.SUBMITTINGcheck inReportReviewScreen.ktwith an added length condition, backed by new validation state inReportReviewViewModel).the button itself is only disabled, some users tap disabled controls or arrive via
accessibility tools) — or, if the button stays enabled and this is meant as inline validation
instead, tapping Submit under-length turns the counter text and the field's outline red.
Resolve which of these two interaction models is intended before implementing (the ticket's
wording suggests the button both stays non-functional under threshold and gives red
error feedback on an attempted tap — clarify with the reporter if ambiguous).
only reads English.
repeated whitespace runs, or otherwise don't resemble real words/language. This needs to stay
a cheap heuristic (e.g. longest repeated-character run, repeated-whitespace run, ratio of
distinct "word" tokens to total length) — not a language model or dictionary lookup. Define
the exact thresholds during implementation and unit-test them against both false positives
(real short complaints) and the obvious abuse cases (
"aaaaaaa...","asdf asdf asdf",etc.). On failure, show exactly: "Please ensure your message is detailed enough for
submission." — deliberately generic, so the heuristic itself isn't disclosed/game-able.
1-character-edit variant of it (insertion/deletion/substitution/transposition — i.e.
Levenshtein/Damerau-Levenshtein distance 1), raise the required length for that submission
from 200 to 2000 characters. Define this as a small pure function so it's unit-testable in
isolation (
ContactPermissionDecision-style, seecontacts/ContactPermissionDecision.ktforthe pattern this repo already uses for this kind of pure decision logic).
OutlinedTextField(standard email-formatvalidation — e.g.
android.util.Patterns.EMAIL_ADDRESS), required for Submit to be enabled.This needs a new field end-to-end:
ReportReviewState.email, a setter on the view model, anda new field on
DebugReport(reporting/DebugReport.kt) so it round-trips throughtoSubmissionPayload()/toStorageJson()/fromStorageJson()— it's a flat JSON DTO with noRoom migration involved, so this is a straightforward addition there.
does not help with lost/forgotten password issues." (Pairs with #161's confirmation copy,
which reiterates that submissions aren't guaranteed a response.)
Acceptance criteria
missing/invalid email, or content that fails the low-effort heuristic.
on the review screen.
low-effort heuristic's true/false positives.
Relevant files
ui/reporting/ReportReviewScreen.kt,ui/reporting/ReportReviewViewModel.kt,reporting/DebugReport.kt,res/values/strings.xml(report_*block).Dependencies / notes
the user is choosing to hand over for a reply, distinct from the report's own best-effort
anonymization). If/when the Cloudflare ingest pipeline (#33/#34) lands, make sure its
PII-anonymization pass (#34) doesn't strip or clash with this intentionally supplied contact
address, and that its handling is covered by the same privacy documentation referenced there.