Session Recap · 23 Jul 2026

Notičky
recheck (GL #199)

Updated: July 23, 2026· Version 1

A pass over the whole notification surface for GL #199 „Zkontrolovat znovu notičky". The PM's one complaint — the candidate instantly gets a „firma už o tom ví"-style message after a match — turned into a full copy + lifecycle audit: reworded the overclaiming push, fixed leftover branding, added a company welcome email, and closed two real correctness gaps (stale iOS badge, push tokens firing after logout). Two deeper items were filed as follow-ups.

01At a glance

Seven commits on feature/199-zkontrolovat-znovu-noticky. Four backend/i18n, two mobile, one spanning both. Not pushed.

7
Commits
4 · 2 · 1
Backend / mobile / both
3
Copy fixes shipped
2
Correctness gaps closed
2
Follow-ups filed

02The #199 complaint, traced

What the candidate actually receives after tapping „Mám zájem“, and why „instant“ is not the bug.

The candidate taps „Mám zájem" → POST /conversations/context. Inside that one request, CreateContextConversationUseCase inserts the conversation row and then dispatches both notifications concurrently in a single Promise.all — one to the candidate (confirmation), one to every company member. No queue, no delay, no ordering guarantee anywhere in the chain.

Candidate
taps „Mám zájem"
POST /conversations/context
one HTTP request
Use case
insert conversation row
Promise.all — both sides
concurrent, no order
Dispatch
candidate: „confirmed"
company: „nová reakce"
The finding

The candidate is told „confirmed" at the same instant the company is notified — not after. And because the company dispatch swallows failures (bad token, DB error), the candidate can be told the company knows when it literally never was notified. The fix is the wording, not the send.

The old title was a second bug on its own: „Kandidát projevil zájem o nabídku" is the company-facing wording, shown to the candidate about themselves in third person. The new title is candidate-facing.

03What shipped

Seven commits, oldest first. Copy and cleanup up top, the two correctness fixes at the bottom.

CommitTypeAreaWhat it does
e15a765afixbackend · i18nReword candidate interest confirmation so it stops claiming the company already saw it; fix the third-person title.
3cccb1cbfixbackend · i18nMagic-link email said „Pracino/Pracina“ — leftover from the rename. Now „Parino/Parina“, matching every other string.
d5244a9echorebackend · i18nDelete dead i18n keys (member-added message, new_message action_label/email_subject) + regenerate types.
d0297b30featbackendCompany variant of the welcome email — „Začni firemním profilem“ + publish-offers copy instead of the candidate CV wording.
6e02c49dfixmobileClear the iOS app-icon badge on launch, on foreground, and on notification open — nothing reset it before.
714167f1fixbackend + mobileUnregister the push device on logout: new DELETE endpoint disables the row; logout fires it best-effort. Has e2e coverage.
db394678refactormobileRemove the unreachable notification-center screen (bell icon, screen, tabs, route, 7 dead i18n keys) — 319 lines out.

04The welcome email split

Company owners were getting candidate copy — „fill in your profile and we'll build your CV“ — on registration.

Account completion sent one welcome email for both roles, with body text written entirely for candidates. A WelcomeRegistrationVariant (candidate | company) is now threaded from the complete-account use case through the email payload into the template, selecting role-appropriate heading and body. The candidate path is unchanged (variant: "candidate").

flowchart LR
A[CompleteAccountUseCase] -->|role| B{variant}
B -->|candidate| C["Začni svým profilem — CV copy"]
B -->|company| D["Začni firemním profilem — publish-offers copy"]

05The two correctness fixes

Not copy — actual wrong behavior found while reading the notification code.

iOS badge (6e02c49d)

The foreground handler set shouldSetBadge: true but setBadgeCountAsync was never called, so a badge from a push stuck forever. Now cleared iOS-only on launch, on every foreground transition, and on notification open. Android is unaffected — its launcher badge is handler-driven.

Logout unregister (714167f1)

A signed-out device kept receiving the old account's pushes — no DELETE endpoint existed and logout never touched push state. Now DELETE /notifications/push-devices/:deviceId disables just that user's row (verified an unrelated account with the same deviceId stays enabled), and logout fires it fire-and-forget so it can never block sign-out. Re-login re-enables via the existing upsert.

06Deferred — filed, not done

Two items surfaced by the audit that need more than a copy edit. Both open, both area:backend, both tagged gl-199.

FYI
IssueTitleWhy it was deferred
pracino-gkcEnrich new-message notification with sender nameBody is generic („Máte novou zprávu v konverzaci“). Needs a repo query change to load the counterpart identity, plus i18n + generated types + the matching email template — real plumbing, not a string swap.
pracino-6isWire job_posting_package_expiring_soon reminder to a cronThe reminder service is fully built but has zero callers. Needs a scheduled job, a cadence decision, and de-dup so a package isn't re-notified every run. It's currently the app's only lifecycle/retention notification — the rest are all event-triggered.
Info

The bigger picture the PM asked about: the backend has exactly one cron today (a monthly unlock-request cleanup) and zero retention/re-engagement notifications. pracino-6is would be the first scheduled notification in the app.

07Status

Where the branch stands.

All seven commits are on feature/199-zkontrolovat-znovu-noticky, tree clean, not pushed. Backend lint + typecheck green; touched unit suites and the full notifications e2e (13/13) pass. Mobile typecheck + lint + jest green. Two pre-existing, unrelated test failures were confirmed to fail identically at HEAD.

One iOS note for the pre-MR checklist: the badge-clear and logout-unregister changes are platform-divergent and worth a device spot-check.