Member identity, promo tools, back-office color, seeded inventory

Real people, not numbers: usernames (unique, profile pane to set them),
shown across the ledger, activity, rosters, and the sidebar chip; vanity
invite links (/join/<username>) with late chain binding intact. Promo tools
pane ships Branded Voice share posts and an email swipe personalized with
each member's link. The earn viewer excludes a member's own campaigns, so
nobody earns from their own spend. Back office gains the cyan/violet/amber
accent family over the green base. Three house campaigns seeded so every
surface shows live inventory. Assets v=20260905i.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-05 06:28:17 -05:00
parent 28ed925443
commit e22021c266
11 changed files with 257 additions and 53 deletions
+9
View File
@@ -70,6 +70,15 @@ async function bootstrap() {
granted_welcome TINYINT NOT NULL DEFAULT 0,
updated BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`);
// additive columns (MySQL 8 has no IF NOT EXISTS for columns)
const alterSafe = async sql => {
try { await q(sql); }
catch (e) { if (!['ER_DUP_FIELDNAME', 'ER_DUP_KEYNAME'].includes(e.code)) throw e; }
};
await alterSafe('ALTER TABLE accounts ADD COLUMN username VARCHAR(30) NULL');
await alterSafe('ALTER TABLE accounts ADD UNIQUE KEY uq_username (username)');
await alterSafe('ALTER TABLE accounts ADD COLUMN member_id INT NULL');
await alterSafe('ALTER TABLE accounts ADD KEY idx_member (member_id)');
await q(`CREATE TABLE IF NOT EXISTS daily_views (
email VARCHAR(190) NOT NULL,
day CHAR(10) NOT NULL,