Skip to content

fix(support): review follow-ups for the ticket attachment field - #893

Merged
TaprootFreak merged 6 commits into
stagingfrom
fix/support-attachment-followup
Aug 7, 2026
Merged

fix(support): review follow-ups for the ticket attachment field#893
TaprootFreak merged 6 commits into
stagingfrom
fix/support-attachment-followup

Conversation

@joshuakrueger-dfx

@joshuakrueger-dfx joshuakrueger-dfx commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Folge-PR zu #890. Behebt die fünf nicht-blockierenden Findings aus der Review dort
(#890 (review)).

Not symptom-driven: Kein Nutzerfall, keine Störung in Produktion. Auslöser ist die Review auf
#890 — ein Approve mit fünf ausdrücklich nicht-blockierenden Findings, die dort nicht mehr
liegenbleiben sollten. Jedes Finding wurde vor dem Bauen am gemergten Stand nachgeprüft; keines
beruht auf einer Weitergabe der Review-Formulierung.

Scale: Finding 1 trifft die Alt-Texte von fünf Handbuch-Bildern, die beim nächsten Deploy
ausgetauscht werden — die Fläche für alle, die die Screenshots nicht sehen können. Finding 2:
der befüllte Anhang-Zustand war in 0 der 35 Matrix-Zellen abgedeckt. Findings 3+4: zwei
Aufrufstellen von Image.file, beide ohne Fehlerbehandlung. Finding 5: eine Zeile.

Smaller fix considered: Zu 1 nur die alt-Attribute statt Alt und Beschreibung — verworfen,
die Beschreibungen zählen die Formularfelder auf und wären danach weiter falsch. Zu 2 eine
einzelne Zelle statt der vollen Matrix — verworfen, die bestehende Gruppe fährt die volle Matrix
und die Kosten sind identisch. Zu 3+4 errorBuilder/cacheWidth an beiden Stellen duplizieren
statt zusammenzuführen — verworfen, das ist genau das Auseinanderdriften, das den doppelten Fix
erst nötig gemacht hat. Zu 5 ist die eine Zeile der Fix.

Was drin ist

  • FilePreviewField (refactor): SupportAttachmentField und FilePickerField trugen einen
    fast zeilengleichen Rumpf. Beide sind jetzt dünne Hüllen um ein gemeinsames Widget und behalten
    ihr eigenes Label und ihre eigene Optik. Image.file existiert dadurch einmal statt zweimal
    und bekommt dort einen errorBuilder (sichtbarer Platzhalter plus Logzeile statt grauer Box) und
    cacheWidth, damit ein 48-px-Thumbnail nicht in voller Auflösung dekodiert wird.
  • Matrix mit befülltem Anhang (test): zweite Gruppe über dieselbe Geräte-×-Textskalen-Matrix,
    mit langem Dateinamen, plus Prüfung, dass das 48×48-Schliessziel tappbar bleibt.
  • Getrimmten Text senden (fix): sendMessage entschied auf trimmed, verschickte aber den
    rohen message — führende und nachlaufende Leerzeichen landeten beim Support. submit() im
    Ticket-Pfad zieht mit: dort prüfte canSubmit längst message.trim().isNotEmpty, gesendet wurde
    trotzdem der rohe Text. Genau dieses Gleichverhalten war TaprootFreaks Grund, den Chat-Befund
    als Lesbarkeitsnotiz statt als Defekt zu führen — es wäre kaputtgegangen, hätte nur der Chat
    getrimmt.
  • Handbuch (docs): Beschreibungen und Alt-Texte für 250/251/252/258/259 kennen jetzt das
    Anhang-Feld und die Büroklammer.

Finding 3 hatte TaprootFreak ausdrücklich als „später" markiert; es ist auf Wunsch mit drin, weil
es Finding 4 auf eine einzige Stelle reduziert.

Verifikation

flutter analyze ohne Befund. 596 Tests grün über test/widgets/form, test/screens/support,
test/screens/settings_user_data und die Goldens beider Flächen, dazu 19 Golden-Tests der
Support-Fläche inklusive des neuen.

Gegenproben, alle nachgefahren:

Mutation Ergebnis
trimmedmessage im Chat zurückgedreht 1 von 20 Cubit-Tests rot
.trim() im Ticket-Pfad entfernt 1 von 80 Support-Cubit-Tests rot
Expanded um den Dateinamen entfernt (analysiert weiter sauber) 35 von 35 Zellen der neuen Gruppe rot
cacheWidth auf 4 1 von 4 Widget-Tests rot
errorBuilder entfernt Corrupt-Test rot

Die Aussage zum Absende-Zustand im Handbuch ist am Golden geprüft, nicht angenommen: die
Anhang-Region ist pixelgleich zum Default-Zustand, geändert sind nur Tags, Nachrichtenfeld und
Schaltfläche.

Baselines

Die bestehenden Goldens sind bewusst nicht regeneriert: der Umbau soll die Optik nicht
verändern, und dass sie unverändert grün sind, ist genau der Beleg dafür.

Sie deckten allerdings nur den leeren Zustand ab — kein Golden im Repo rendert je eine
ausgewählte Datei, und genau dort ändert cacheWidth die Dekodier-Auflösung. Deshalb kommt ein
neues Golden dazu: das Ticket-Formular mit gesetztem Anhang
(support_create_ticket_page_attached).

Der Weg dorthin ist nicht trivial: Image.file dekodiert auf der echten Async-Zeitachse, und der
gemeinsame precacheImages-Helper endet in pumpAndSettle, das hier nicht zurückkehrt. Eine
feste Warteschleife wäre die schlechteste Lösung gewesen — sie hätte auf einem langsameren Runner
ein leeres Thumbnail als Baseline eingefroren, grün. Der Test pollt daher auf das dekodierte
RawImage und assertiert es; mit Poll-Budget 0 wird er rot. Ein langsamer Runner scheitert damit
laut, statt ein falsches Bild festzuschreiben.

Die Baseline selbst erzeugt golden-regenerate.yaml auf dem self-hosted Runner, nicht mein Mac —
sonst rendert sie unter einer anderen Toolchain als der, die sie auf PRs validiert. Bis dieser
Lauf durch ist, ist Visual Regression erwartbar rot.

Finding 3 ist damit erledigt, Finding 4 an der verbliebenen Stelle behoben.

SupportAttachmentField and FilePickerField carried a near line-identical
body (48 px thumbnail, file name, edit icon, placeholder row). Both are
now thin wrappers around FilePreviewField and keep their own label, so
the KYC and support surfaces render exactly as before — proven by the
unchanged goldens.

With one call site left, Image.file gains the two things it lacked in
both: an errorBuilder, so an unreadable temp file (iOS reclaims those)
shows a placeholder instead of a grey box plus a logged exception, and
cacheWidth, so a 48 px thumbnail no longer decodes at full resolution.
The matrix only ever pumped a state without an attachment, so the
placeholder row was gated and the filled one — 48 px thumbnail, an
Expanded file name and two icons, one of them a 48x48 tap target — was
not. The second group runs the same device x text-scale sweep with a
long file name and additionally asserts the close target stays
tappable.

Counter-probe: dropping the Expanded around the file name turns 35 of
the 35 new cells red while the code still analyzes clean.
sendMessage decided on trimmed but posted the raw message, so leading
and trailing whitespace reached support. Counter-probe: restoring the
old expression turns 1 of the 20 cubit tests red.
…erclip

Five of the goldens regenerated in #890 are mapped into the handbook and
now show the attachment field and the paperclip button, while the prose
still enumerated the old form fields. These descriptions are the alt
text for readers who cannot see the screenshots, so the gap hits exactly
the people who depend on them.

The submitting state was checked against the golden rather than assumed:
the attachment region is pixel-identical to the default state, so only
the tags, the message field and the button change.
No golden rendered a selected file, so the thumbnail path this PR touches
had no baseline at all — the one state where cacheWidth changes the decode
resolution.

Image.file decodes on the real async timeline and the shared precacheImages
helper ends in pumpAndSettle, which does not return here. The test therefore
polls for the decoded RawImage and asserts it, so a slower runner fails
loudly instead of capturing an empty thumbnail as the new baseline. With the
poll budget set to zero the test goes red.

The baseline PNG was rendered by golden-regenerate.yaml on the self-hosted
runner, under the same toolchain that validates it; it is carried here byte
for byte so the branch keeps a fully signed history.
@joshuakrueger-dfx
joshuakrueger-dfx force-pushed the fix/support-attachment-followup branch from f48fa55 to 3fd3f6e Compare August 4, 2026 15:25
TaprootFreak added a commit that referenced this pull request Aug 4, 2026
…asset smoke

The two README mentions now use the canonical four-gate phrasing, the
paths-filter comment admits the self-entry, and the smoke description
states what the HTTP probes actually prove: per-file existence comes
from the assembly count guards, the probes only see the auth gate answer
(401 arrives before nginx looks any file up — tracked in #893).
@TaprootFreak

Copy link
Copy Markdown
Contributor

Note: commit b571848 (PR #892) references this issue by mistake — the intended reference is #894 (handbook build check asset smoke). Sorry for the noise.

@joshuakrueger-dfx
joshuakrueger-dfx marked this pull request as ready for review August 4, 2026 15:50
TaprootFreak added a commit that referenced this pull request Aug 4, 2026
…od deploys (#892)

## Summary

Two handbook changes following up on #885/#889:

### Walkthrough layout (user feedback on the live page)

The three images of the insider-unlock explainer rendered neither
cleanly side by side nor stacked: the flex items are caption-driven
wrapper divs, so two images filled the row and the third wrapped below.
The block now shows the **instructions first**, then the three images in
an equal-thirds grid with captions wrapping under their image; below
700px the walkthrough stacks as full-width rows (matching the page's
existing narrow-viewport behaviour, verified against a neighbouring
section). Desktop and mobile renders were verified in a local browser
before pushing; the final CSS-cascade cleanup is render-identical
(byte-equal screenshots).

### Deploys: staging → production, DEV retired

Every push to `staging` now deploys directly to handbook.realunit.app
(`:latest`, PRD secrets). The `deploy-dev` job, the `develop` trigger
and the `:beta` tag are removed, and the `paths:` filter is dropped
deliberately: every staging merge ships, which also re-stages the
api-/web-sourced handbook content the filter could never observe. All
prose that described the old staging→DEV/develop→PRD split (workflow
comments, both READMEs, three spots inside the handbook page itself) now
describes the single production lane, and the build-check comments state
precisely what that check gates (screenshot-assembly, store-listing
sync, legal-sync, image build + container smoke) versus what only the
deploy run exercises (SSH/secrets/rollout).

The retired dev-handbook instance keeps serving its last state until it
is decommissioned on the infrastructure side (container + DNS) —
intentionally out of scope for this repo. The four `DEPLOY_DEV_*` repo
secrets become unreferenced with this PR and can be deleted afterwards.

## Erratum

Commit `b571848f` references issue #893 in its message; the intended
reference is **#894** (pre-existing: the build check's asset smoke
accepts 401 as existence proof, which the auth gate makes meaningless —
found during the review passes here, tracked separately). Amending a
pushed commit is out per repo policy, hence this note.
The previous commit made sendMessage post the trimmed body, but submit()
still hoisted the raw one — so the chat trimmed and the ticket did not.
That split is worse than the state before: the two paths behaving alike
was the reason the chat side was a readability note rather than a defect.

canSubmit already gates on message.trim().isNotEmpty, so trimming what is
sent changes no acceptance decision, only what support receives.

Counter-probe: dropping .trim() again turns 1 of the 80 support cubit
tests red.
@TaprootFreak
TaprootFreak merged commit eaba98d into staging Aug 7, 2026
6 checks passed
@TaprootFreak
TaprootFreak deleted the fix/support-attachment-followup branch August 7, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants