feat(banner): per-campaign colour overrides and urgent alerts
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
Non-default campaigns gain an optional colour override (background / text / link) in two sets — one for every theme, one for the dark theme only — and an "urgent" flag. - Colours ride profile.get as six trailing FlatBuffers strings on BannerCampaign (backward-compatible). The client resolves the cascade (dark <- dark ?? all, light <- all) per rendered theme and derives the strip border from the background in JS (no CSS color-mix, for the old Android WebView floor); AdBanner applies them as inline vars scoped to the strip. - Urgent is resolved entirely server-side: while any enabled, in-window urgent campaign exists, computeActiveSet returns only the urgent campaigns and bannerFor skips the eligibility gate — so a system notice reaches every viewer (paid / hint-holding / no_banner included) and preempts the ordinary feed. No wire field; it appears on each viewer's next profile.get. - Admin console (/_gm/banners): native colour pickers + a live light/dark preview of the strip, and an urgent toggle. The default campaign stays plain, enforced by the service and a DB CHECK. Migration 00009 is additive (nullable colour columns + a bool default + all-or-nothing / hex / default-plain CHECKs) — expand-contract, rollback-safe. Docs: ARCHITECTURE §10, UI_DESIGN, FUNCTIONAL (+ru). Tests: ads unit (urgent preempt + colour validation), codec + resolver unit, gateway transcode, and integration (colour round-trip + urgent bypass against real Postgres).
This commit is contained in:
@@ -647,7 +647,10 @@ describe('codec', () => {
|
||||
const uid = b.createString('u-1');
|
||||
const m0 = b.createString('promo-en');
|
||||
const msgs = fb.BannerCampaign.createMessagesVector(b, [m0]);
|
||||
const camp = fb.BannerCampaign.createBannerCampaign(b, 3, msgs);
|
||||
fb.BannerCampaign.startBannerCampaign(b);
|
||||
fb.BannerCampaign.addWeight(b, 3);
|
||||
fb.BannerCampaign.addMessages(b, msgs);
|
||||
const camp = fb.BannerCampaign.endBannerCampaign(b);
|
||||
const camps = fb.BannerInfo.createCampaignsVector(b, [camp]);
|
||||
const banner = fb.BannerInfo.createBannerInfo(b, camps, 60000, 5000, 40, 1000, 250, 1000);
|
||||
fb.Profile.startProfile(b);
|
||||
@@ -656,7 +659,8 @@ describe('codec', () => {
|
||||
b.finish(fb.Profile.endProfile(b));
|
||||
|
||||
const p = decodeProfile(b.asUint8Array());
|
||||
expect(p.banner?.campaigns).toEqual([{ weight: 3, messages: ['promo-en'] }]);
|
||||
// A plain campaign carries no override (both sets null → the neutral theme tokens).
|
||||
expect(p.banner?.campaigns).toEqual([{ weight: 3, messages: ['promo-en'], overrideAll: null, overrideDark: null }]);
|
||||
expect(p.banner?.timings).toEqual({
|
||||
holdMs: 60000,
|
||||
edgePauseMs: 5000,
|
||||
@@ -667,6 +671,26 @@ describe('codec', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('decodes a campaign colour override (both sets)', () => {
|
||||
const b = new Builder(256);
|
||||
const uid = b.createString('u-1');
|
||||
const m0 = b.createString('branded');
|
||||
const msgs = fb.BannerCampaign.createMessagesVector(b, [m0]);
|
||||
const s = (v: string) => b.createString(v);
|
||||
const [obg, ofg, ol, obgd, ofgd, old] = ['#aa0000', '#ffffff', '#ffdd00', '#330000', '#eeeeee', '#ffcc00'].map(s);
|
||||
const camp = fb.BannerCampaign.createBannerCampaign(b, 1, msgs, obg, ofg, ol, obgd, ofgd, old);
|
||||
const camps = fb.BannerInfo.createCampaignsVector(b, [camp]);
|
||||
const banner = fb.BannerInfo.createBannerInfo(b, camps, 60000, 5000, 40, 1000, 250, 1000);
|
||||
fb.Profile.startProfile(b);
|
||||
fb.Profile.addUserId(b, uid);
|
||||
fb.Profile.addBanner(b, banner);
|
||||
b.finish(fb.Profile.endProfile(b));
|
||||
|
||||
const p = decodeProfile(b.asUint8Array());
|
||||
expect(p.banner?.campaigns[0].overrideAll).toEqual({ bg: '#aa0000', fg: '#ffffff', link: '#ffdd00' });
|
||||
expect(p.banner?.campaigns[0].overrideDark).toEqual({ bg: '#330000', fg: '#eeeeee', link: '#ffcc00' });
|
||||
});
|
||||
|
||||
it('leaves the profile banner undefined when absent', () => {
|
||||
const b = new Builder(64);
|
||||
const uid = b.createString('u-1');
|
||||
|
||||
Reference in New Issue
Block a user