- Shipped
- September 7, 2026 at 9:07 AM UTC
- Author
- Kamo
- Commit
- 8da70d9
Contact effects were a single per-member setting on one organization page. They now resolve exactly the way event sounds do — shipped catalogue, then the platform, then the organization, then the member — with a pane for each layer and Inherit above No effect on every one below the top. Platform Administration → Configuration → Contact Effects (new tab) Settings → Essential → Behavior & Labeling (the organization's layer) Settings → Member → Contact Effects (new tab, on your own profile) EVERY EVENT RESOLVES INDEPENDENTLY. A member who picks confetti for one contact keeps their organization's choice for the other five and the platform's for whatever the organization never touched. The alternative — copying the resolved set into a layer on save — would freeze that member at whatever shipped the day they first opened the pane, and nothing an administrator changed afterwards would ever reach them. `null` and absent both mean inherit, and neither is interchangeable with a value: `none` is a decision to paint nothing and it survives a lower layer saying otherwise. That is the whole difference between "I don't mind" and "not for me", and it is asserted. Inherit says what it would give you — "Inherit — Chime", "Following your organization: Chime. Change it there and this follows." Offering it as a bare word asks somebody to choose blind. The platform pane has no Inherit at all: there is no layer beneath it but the catalogue, and its reset control is what returns to that. One editor serves all three panes; they differ only in which layer they write and whether Inherit is offered. Three near-identical editors is how the three drift apart. The platform defaults are now reticle → nova for the left button, aperture → burst for the middle, and pool → droplet for touch. Chosen as a SET rather than as six favourites: a press pulls energy in and a release lets it out, so a click still reads as one gesture, and none of the three pairs is round, which keeps a shell that is mostly rectangles from looking like it is blowing bubbles. Backed by SecurityService's new /api/security/contact-effects (a31a3fb..88c9566) over its own CONTACT_EFFECT_PROFILES table, gated for the platform layer by the new MANAGE_PLATFORM_CONTACT_EFFECTS right. The member scope resolves to the caller's own id from the session, so the member tab is read-only on somebody else's profile and the service would refuse the write regardless. Two defects found by rendering the panes rather than reasoning about them. The platform pane displayed "Inherit — Reticle" for events it had not set, while offering no Inherit option — a value absent from the options list is one nobody can return to; it shows the catalogue value as the standing choice now. And every pane previewed the FULLY resolved effect, so an administrator setting the organization default saw whatever their own member override happened to paint under their own The layer store is a module-level cache plus a window event rather than a React context: the canvas is mounted as the last child of <body>, outside every provider, because it must also cover /validate and /logout. Every failure path lands on the shipped defaults, silently — this loads on pages with no session, so a 401 is the normal case. Not migrated: the member's old `contactEffects` ui-preferences key, written only by the previous single-layer pane during the day it existed. It is orphaned rather than read, and everyone starts from the layered defaults.