Files
Caprika f5707c8cae fix(cms): keep campaign touchpoints on home and off the wake path (#8269)
* fix(cms): host campaign touchpoints on the home view only

Every placement this app authorizes is a home placement (`opend.home.*`),
but the hosts did not follow. The modal pair mounted on every route except
onboarding, and the account badge rode the entry rail across all of its
tabs plus the project workbench corner — so a CMS campaign could open over
an open project, which is where a user reported meeting it.

The hosts now answer to the home view: App mounts the Test and Production
modals only there, EntryShell gates the badge and the hover entry on
`view === 'home'`, and the workbench cluster carries no CMS touchpoint at
all. The built-in DeepSeek pill is product chrome rather than a CMS host
and keeps its reach across entry tabs and project detail; the source
contract that guarded that reach is split to say so.

* fix(cms): stop a wake from re-presenting a displayed campaign

Screen sleep hides the page, which withdraws the lease and takes the modal
down. Waking refreshes — and the refresh carried the withdrawn lease as its
revalidation subject, which the impression gate read as "the active
activity" and exempted from the device impression. The server offers the
same activity, so the campaign re-opened on every wake, for as long as it
was published.

The exemption now belongs to the presentation still on screen and nothing
else. A page that goes hidden releases that presentation, so the offer
arriving on wake is a new one and the impression closes it. A suppressed
offer has to clear rather than retain: retaining republishes the very lease
the visibility fence just withdrew, which was how the first cut of this fix
kept the modal on screen. Retain stays for the case it was written for — an
offer arriving beside a live presentation, which keeps its mount.

* fix(cms): keep a displayed campaign through a recoverable poll failure

The lifecycle draws a line no other layer may redraw: only the server's own
withdrawal ends a live lease, and a transport failure rides out on the lease
already granted, retried inside the same cycle. The modal's `onError` ignored
that line and released its open presentation for any error at all.

That was harmless while the impression gate still exempted the active lease.
Keying the exemption on the presentation alone — which is what stopped a wake
from re-presenting a displayed campaign — made the two disagree: a poll that
failed on a dropped connection left the modal mounted with no presentation to
show for it, so the retry that recovered the very same activity read the device
impression, found nothing on screen, and cleared the host. The campaign
vanished mid-flight on ordinary network noise and could not return for the rest
of the run.

The presentation now follows the lease it belongs to, withdrawn only by a
withdrawal. The wake fence still releases it on hidden, because that
presentation is genuinely over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(cms): retain presentation with active lease

* test(cms): await mounted presentation before refresh

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: ivy-ting <234137810+ivy-ting@users.noreply.github.com>
2026-09-19 11:28:37 +00:00
..