Marketing Command Center

repo: ~/Projects/marketing-command-center · 186 commits · 4 in last 24h, 45 in last 7d · █▄ ▁ ▂

Search the build

186 commits indexed

  1. 39fcdef 2026-07-27 MCC bluesky: fail loud if image can't embed — never silently post text-only
  2. f4429c8 2026-07-27 auto-save: 2026-07-27T15:21:37 (1 files) — data/cc-warmup-seed-200.json
  3. a9436a5 2026-07-27 auto-save: 2026-07-27T14:51:27 (1 files) — data/cc-import-activities-audit.jsonl
  4. d530fe4 2026-07-27 auto-save: 2026-07-27T14:21:14 (2 files) — data/cc-import-fm-clients-hold.json data/cc-import-fm-clients-mailable.json
  5. 747d83a 2026-07-26 auto-save: 2026-07-26T04:40:35 (1 files) — public/data/clients-manifest.json
  6. 38e14e7 2026-07-22 auto-save: 2026-07-22T12:44:11 (1 files) — package-lock.json
  7. 85f3566 2026-07-22 chore: version bump (session close)
  8. 5d1c41b 2026-07-22 chore(mcc): move IG re-pull schedule from Mac2 launchd to Kamatera-local cron
  9. 6b2db25 2026-07-22 auto-save: 2026-07-22T12:13:58 (1 files) — data/ig-repull-status.json
  10. 21be5cc 2026-07-22 feat(mcc): board shows IG re-pull health at a glance + Run-now fix button
  11. 6d2f55b 2026-07-22 chore(mcc): daily launchd job for the IG media re-pull (Mac2 → Kamatera SSH)
  12. 214b1bb 2026-07-22 fix(mcc): IG re-pull only touches at-risk images, never clobbers stable URLs
  13. 971082a 2026-07-22 feat(mcc): IG Graph API re-pull to restore durable thumbnails for published IG posts
  14. 2ffb36f 2026-07-22 fix(mcc): durable local cache for expiring IG/fbcdn post images + graceful placeholder
  15. 243d9c5 2026-07-22 fix(mcc): guard panel-router race that blanked panels + untrack screen-record videos
  16. 5a92b90 2026-07-22 auto-save: 2026-07-22T10:13:11 (5 files) — scripts/repro-run3.js screenrecord/DEBUG-REPORT.md scripts/probe-403.js scripts/verify-fix.js scripts/verify-fix2.js
  17. c073e61 2026-07-22 auto-save: 2026-07-22T09:43:01 (8 files) — public/app.js screenrecord/ scripts/nav-map.js scripts/probe-403-and-posts.js scripts/repro-race.js
  18. 355b730 2026-07-22 auto-save: 2026-07-22T08:42:49 (1 files) — package-lock.json
  19. 0eff96e 2026-07-22 chore: v1.7.0 (session close — Threads un-gate, image+SKU generation, honest postable status, /publish skip)
  20. 29d1ff2 2026-07-22 channels: /publish skips non-postable + media-mismatched channels
  21. 69af9fe 2026-07-22 channels: add honest 'postable' status — connected != can-actually-post
  22. d84dc2a 2026-07-22 engine: untrack runtime engine-queue.json + protect it on deploy
  23. d9e22d3 2026-07-22 engine: image-grounded captions (vision) + always include DW SKU
  24. 7ff5d7c 2026-07-22 deploy: ship Threads un-gate to Kamatera MCC (app 69 reloaded, health 200)
  25. 2545273 2026-07-22 engine: stop birth-gating Threads suggestions (DW is a Meta tester, publishes without App Review)
  26. 5459b68 2026-07-21 LinkedIn: filed Developer Support ticket CAS-11523819 to unblock CMA (auto-added defaults on app 257140099)
  27. aae7c9c 2026-07-21 LinkedIn: record CMA self-serve blocker (auto-added default products grey Request access; app 257140099 created+verified, client 86z1h5ic86lw65, org 3090253)
  28. 2becbe5 2026-07-21 submissions: Facebook pages_manage_posts App Review package (dashboard steps + use-case + screencast script + reviewer repro)
  29. eb16f00 2026-07-21 submissions: LinkedIn dedicated-app + Community Management API request package (new-app spec + email-verify + post-approval finish)
  30. 4e9c425 2026-07-21 submissions: TikTok production App Review package (fields + scope text + demo shot-list + submit steps)
  31. 33d5174 2026-07-21 chore: v1.6.3 (session close — FB page names fix)
  32. 3779b0f 2026-07-21 engine: add LinkedIn as 7th channel — image-upload adapter (initializeUpload→PUT→post), status entry, trade-voice captions, channel backfill for already-generated days
  33. 71a303c 2026-07-21 5x: Engine panel verified clean on live marketing.dw (0 app defects; 2 harness fixes, 1 phantom discarded)
  34. 024a26b 2026-07-21 channels: show real Facebook Page names instead of raw IDs in status panel
  35. 0210a80 2026-07-21 engine: contrarian fixes — YT approve defaults private, edit routed through CAS (double-post guard), TZ pinned PT, productUrl in outbound content (+bluesky budget), LLM claim-grounding
  36. 0b73831 2026-07-21 engine: brain (candidates+llm+generate), scheduler (CAS tick+crash sweep), Engine panel UI
  37. 0938d51 2026-07-21 engine: core module (store/config/skeleton) + channels.postLive + leak-deny lib
  38. e8501e8 2026-07-21 assets: local qwen2.5vl vision enrichment (aiTags) + search
  39. 70479a4 2026-07-21 youtube: add force-ssl scope for videos.update (visibility control)
  40. e10410d 2026-07-21 youtube: add /youtube/visibility endpoint (flip private↔public via videos.update)
  41. 85d08d7 2026-07-21 youtube: build resumable videos.insert upload pipeline (postYouTube) + token refresh
  42. ed9e2e2 2026-07-21 channels: derive TikTok + YouTube account handles from env (accounts rail was blank for both)
  43. ca60a5c 2026-07-21 chore: v-bump (session close — TikTok token-mirror fix)
  44. 34ea50f 2026-07-21 TikTok App Review: mark posting pipeline VERIFIED (creator_info ok); demo needs only a hosted video + gated test-post
  45. a9c34db 2026-07-21 Add TikTok App Review submission package (production fields + demo script + posting-pipeline prereq)
  46. 63d0cd0 2026-07-20 TikTok: mirror OAuth token into env on callback (fixes Bearer undefined on post) + demo recorder scaffold
  47. 9a142fd 2026-07-20 auto-save: 2026-07-20T15:02:48 (1 files) — package-lock.json
  48. 76de953 2026-07-20 chore: v1.6.1 (session close — Schumacher calendar filter)
  49. 5ea8d24 2026-07-20 calendars: filter Schumacher (internal-only) out of both calendar layers at source
  50. 540ae08 2026-07-20 chore: lint + refactor + v1.6.0 (session close — reel/FB-page picker hardening)
  51. 97dcfe7 2026-07-20 Composer: add reel/story asset picker + Facebook Page selector
  52. 02a76e0 2026-07-19 auto-save: 2026-07-19T07:55:24 (1 files) — package-lock.json
  53. 947d85f 2026-07-19 chore: v1.5.0 (session close — Clients & Prospects panel, Threads accounts, gated CC import staging)
  54. 861cc69 2026-07-19 auto-save: 2026-07-19T04:54:45 (1 files) — public/data/clients-manifest.json
  55. d77d6ee 2026-07-17 chore: lint, refactor (drop dead env()), v1.4.0 (session close)
  56. c1d7a1d 2026-07-17 instagram: poll media container status until FINISHED before publish — fixes 'Media ID is not available' (Meta processes images async)
  57. 47459ec 2026-07-17 cc-import: stage gated FM-client → Constant Contact import (34,825 contacts; scraped list excluded); upload drafted to pending-approval (DTD verdict A)
  58. c333624 2026-07-17 auto-save: 2026-07-17T09:46:05 (2 files) — modules/channels/index.js public/panels/composer.js
  59. 19aea36 2026-07-17 bluesky: rich-text FACETS — #hashtags + links now clickable (UTF-8 byte-offset aware per docs.bsky.app)
  60. c16d138 2026-07-17 Vendor IG: mark BN Walls 'none found' (no Business-Discovery-readable IG); drop orphan cache entry
  61. 3c65730 2026-07-17 auto-save: 2026-07-17T09:16:00 (1 files) — package-lock.json
  62. 01590c1 2026-07-17 chore: lint (with-open autofix), refactor (none needed), v1.3.0 → v1.3.1 (session close)
  63. 959a2f0 2026-07-17 Vendor IG Reporting: wire never-expiring page token, page-token discovery, standalone refresh CLI; populate 33/34 vendors
  64. b69f993 2026-07-17 MCC Clients: server-side notes store so notes sync across devices
  65. 8738173 2026-07-17 deploy: exclude live-authoritative runtime data (outbox/tokens/pages/assets) from rsync — deploys were clobbering the live Meta token, staged posts, and UI-added assets
  66. 2888aa4 2026-07-17 board UX: add 'Clear staged' — dismisses stale staged/pending/failed cards (keeps posted history) via new POST /api/channels/outbox/clear; Compose links to Composer
  67. 075ec0c 2026-07-17 Vendor IG Reporting: render last 3 posts inline per vendor (was 10-photo grid)
  68. 922c32f 2026-07-17 clients panel: chip clicks open target in a right-side drawer (website embeds live; LinkedIn/IG show one-click card since they block framing)
  69. 2aac093 2026-07-17 MCC Clients: per-contact notes field below each firm card
  70. 30afe18 2026-07-17 MCC Clients: show full address + phone + real domain + best contact/follow per firm
  71. 7877a92 2026-07-17 composer: banner preview respects template aspect-ratio (#2) + warns when no asset selected (#3) — contrarian findings. Font-fail finding (#1) verified PHANTOM (allow-same-origin loads fonts), dropped.
  72. 01d65e1 2026-07-17 composer: Banners tab renders a LIVE banner preview (real /api/layouts/render HTML in the middle zone) instead of a dead deep-link
  73. a4a92b1 2026-07-17 layouts: fix malformed placeholder SKUs — DW-GL-001 → DWGL-100001 (real DW format DW<code>-######)
  74. 4877837 2026-07-17 5x: contrarian gate resolved — generate+publish now functionally verified end-to-end
  75. f3e5129 2026-07-17 composer: Generate copy now sends kind=instagram (was 'social' → silently degraded to headline). Contrarian catch.
  76. 27b2af2 2026-07-17 5x: composer clean-twice report
  77. 917acca 2026-07-17 5x sweep 4: add inline SVG favicon — kills the app-wide /favicon.ico 404 (last console error)
  78. c5779ce 2026-07-17 5x sweep 2: composer drops broken asset thumbnails (dead URLs no longer 404-spam the console or show broken tiles)
  79. 8b2fd01 2026-07-16 composer: new central board — combine asset/banner + copy → publish to all connected socials at once
  80. 5e38c36 2026-07-16 clients panel: one-click 'Copy not-actioned emails' (respects search+filters, skips already-actioned)
  81. 05fccc4 2026-07-16 clients panel: 'not actioned' filter + LinkedIn/Instagram actioned timestamp columns in CSV export
  82. 88eb92d 2026-07-16 clients panel: inline LinkedIn/Instagram Follow chips under each name + date-time stamp on Follow/message click (persisted per contact/channel)
  83. bb5f304 2026-07-16 docs: IG fleet connect checklist (35 live + convert/link steps for the rest)
  84. bec82b0 2026-07-16 channels: add /ig-audit — enumerate ALL Instagram accounts (page-linked + business owned/client), not just the 34 page-linked
  85. a8a2166 2026-07-16 auto-save: 2026-07-16T16:43:46 (1 files) — package-lock.json
  86. 25362f4 2026-07-16 chore: lint, refactor, v1.3.0 (session close)
  87. 1473c68 2026-07-16 auto-save: 2026-07-16T12:13:12 (1 files) — public/data/clients-manifest.json
  88. 5723126 2026-07-16 clients panel: coverage summary (contacts/emails/IG/LinkedIn across all groups); manifest carries per-group counts
  89. b1d1fc7 2026-07-16 clients panel: dedup + field-merge in All Prospects (LA groups overlap; keeps richest contact record)
  90. d544ba1 2026-07-16 clients panel: merged ★ All Prospects group (search/sort/filter/export across all ~5.9k firms; each tagged by city)
  91. 63885bc 2026-07-16 clients panel: quick filters (email/IG/LinkedIn/uncontacted), Copy-all-emails, IG+LinkedIn count pills, density toggle (completes sort+density rule)
  92. d885131 2026-07-16 auto-save: 2026-07-16T11:43:05 (1 files) — public/data/clients-manifest.json
  93. 1a67441 2026-07-16 clients panel: search across ALL fields + sort dropdown (name/city/state/has-email/IG/LinkedIn), persisted
  94. e0ae6f5 2026-07-16 clients: browser+local-model contact enrichment (direct Instagram/LinkedIn + email gap-fill)
  95. ad37a60 2026-07-16 refresh-clients.sh: read CSVs from project data/sources (launchd can't read ~/Downloads — TCC), nullglob rsync
  96. 87dc441 2026-07-16 calendars: decouple launched layer so it stays live where bulk_fivefield_worklist is absent (Kamatera)
  97. c1ee3c8 2026-07-16 clients panel: per-group CSV export (respects filter; includes emails + LinkedIn/IG URLs; local download, not CC)
  98. b50489a 2026-07-16 refresh-clients.sh: mkdir lock (macOS has no flock)
  99. 09230f0 2026-07-16 refresh-clients.sh: pin PATH for launchd (node/python/homebrew)
  100. 2ba021c 2026-07-16 register clients module in registry so the panel mounts in the nav
  101. 0ed9d95 2026-07-16 clients panel: FM client DB (43k) + CSV prospect groups, LinkedIn+Instagram discovery
  102. bfbe222 2026-07-16 followlist: add build:followlist refresh script + CRCP broker section
  103. e347c43 2026-07-16 linkedin panel: add Follow list mode (ported from li-clicklist.html)
  104. 063dc28 2026-07-16 All Calendars: add Launched layer — already-live products on their launch date, own modal, v1.2.0
  105. d28f0d1 2026-07-16 PR naturals mailer: SISAL search link to page 1 (verified 1002 results, sisal SKUs on top)
  106. b3300df 2026-07-16 PR naturals mailer: point SISAL tile at sisal search (page 5), keep pr-naturals UTMs
  107. f2866e6 2026-07-16 chore: harden calendars module (pool error log, load() try/catch, fieldvis merge), v1.1.0 (session close)
  108. a83ad8f 2026-07-15 Place All Calendars in the Planning nav group (next to Marketing Calendar)
  109. b93e4e1 2026-07-15 Untrack regenerable 13MB activation snapshot (gitignored — was committed by add -A)
  110. c3e1ea5 2026-07-15 All Calendars: front-end panel (facet rail + multi-layer month grid + drag reschedule)
  111. bd25186 2026-07-15 channels: resolve IG per-page (bulk field dead) + add /meta-token paste-ingest route to bypass blocked OAuth redirect
  112. fea8d92 2026-07-15 All Calendars module: backend (activation projection + shopify enrichment + marketing/campaign overlays)
  113. 81c03cf 2026-07-15 channels: Meta connect now upgrades short→long-lived token + re-caches all 80 Pages so the whole IG fleet comes back permanent (not 1-hour)
  114. 9ef59f0 2026-07-15 Add deploy/apply-cutover.sh — paste-safe one-command nginx cutover/restore (backup+install+test+reload+verify)
  115. 4a03044 2026-07-15 accounts panel: use live Meta token-health so a dead token flips to needs-creds instead of showing green
  116. a26137e 2026-07-15 Fix staged vhost: MCC front door + public /reels/*.mp4 + /privacy + /terms passthroughs to reels app (:9848) — restores media/legal fetch after the domain flipped to the MCC without them
  117. 49dbc5c 2026-07-15 chore: v1.0.2 (session close — PR naturals draft iterations)
  118. eb09669 2026-07-15 Stage DW-marketing consolidation (DTD verdict A): nginx vhost swap → MCC on DW domain + agentabrams 301 + CUTOVER runbook (staged, not applied)
  119. 98f32bf 2026-07-15 PR naturals v3 design: uniform 620x620 tiles (grasscloth source was 527px, CDN can't upscale -> swapped to full-size original)
  120. e98234f 2026-07-15 texture tiles → vendor+tag collection links (browser-verified via CTA agent)
  121. a71c5e9 2026-07-15 naturals mailer: swap hero to Bellagio Weave roomset (pattern repeat issue on flat swatch)
  122. a7aea85 2026-07-15 PR naturals v3 — logo masthead, 888 phone, appointment banner, uniform square tiles, minimal footer
  123. 1a54554 2026-07-15 PR naturals v2 mailer — 6 material categories from the July drop, dark editorial layout
  124. 778a375 2026-07-15 Phillipe Romano naturals campaign draft — Bellagio roomset hero + 6-series grid
  125. b904d73 2026-07-15 grasscloth campaign draft — Zoffany Kensington hero + Designtex colorway grid
  126. 0f129eb 2026-07-15 add reels module — embed dw-marketing-reels console via same-origin proxy (Range passthrough for video seek); add fix-connie-creds.sh
  127. 0f46bdd 2026-07-15 auto-save: 2026-07-15T11:36:14 (1 files) — scripts/fix-connie-creds.sh
  128. f2636e3 2026-07-15 channels: Instagram-account selector — pick 1-to-all IG accounts
  129. df1e623 2026-07-14 chore: lint fixes (jget r.ok guard, .env value quoting per unquoted-sourcing rule, comment typo), v1.0.1 (session close)
  130. f872cf0 2026-07-14 channels: three-valued is_valid guard so a Graph rate-limit/error can't false-flip a valid Meta token to needsReconnect
  131. c60d8ff 2026-07-14 Expand Sounds catalog: +6 royalty-free libraries & SFX, live TikTok trending-sounds link, Grateful Dead essential-eras section (30 sources)
  132. 8bb0bc5 2026-07-14 Add Sounds & Assets panel: curated free/royalty-free music + TikTok assets, brand-safety badged (Grateful Dead archive listen-only)
  133. fa0d1c1 2026-07-14 deploy: ship Accounts panel to Kamatera; fix .deploy.conf HEALTH_URL to :9662 (was pointing at vendor-scrapers on 9661)
  134. 3b13d2e 2026-07-14 Add Accounts panel: Amazon-style social-account filter rail with connection status + inline how-to-fix credentials
  135. 897ebac 2026-07-03 chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)
  136. 67b4c0c 2026-06-30 auto-save: 2026-06-30T12:40:24 (1 files) — scripts/cc-agent-discover.js
  137. fa747ed 2026-06-24 deploy.conf: fix MCC health URL to :9661 + guard gitignored follow-counts data from rsync --delete
  138. 90978c9 2026-06-24 Add Followers/Following tab to DW Marketing Command Center
  139. eac09ca 2026-06-24 CC module: proxy Connie cc-agent when no local CTCT token (DTD verdict C)
  140. 88a51a7 2026-06-22 Social: real approval pipeline + instant posting + truthful live/simulated UX
  141. fe2afd8 2026-06-17 meta-token-health: fix dead George alert URL (HTTPS flip 2026-06-17)
  142. de6eb5f 2026-06-16 Security: remove browser-shipped basic-auth password from client boot-error (was the now-rotated DWMarketing2026!) — gitleaks-flagged browser-shipped cred in public/app.js
  143. ad0fc2a 2026-06-16 Add META token health canary (Council #4): GET /me liveness + expiry warn + heartbeat stamp; alerts CNCP+George on expiry (the silent failure that darkened the Vendor IG panel)
  144. 938df84 2026-06-16 Code-health: gitignore *.bak + runtime social-queue.json (untrack it), add rel=noopener to activation-page link
  145. 78312e8 2026-06-16 Vendor IG roster: add web-confirmed @schumacher1889 (was 'no IG found'); 34/38 now have IG
  146. 96afa0a 2026-06-16 Vendor IG roster: add last-10-posts grid per vendor via Instagram Business Discovery (server-side, cached, graceful degrade on expired token)
  147. 8e94008 2026-06-15 Channels: add Bluesky (live, app-password) + Threads (review-gated) publishers; board shows them as real columns
  148. e76aecd 2026-06-15 Add Meta App Review prep doc (System-User route + full review steps, paste-ready justifications)
  149. 5435736 2026-06-15 Channels: add Facebook/Instagram Page picker — select WHICH of your 80 Pages (+ 11 linked IG) to post to.
  150. a32a889 2026-06-15 Add Streams Board — Hootsuite-style column view (social channels + Constant Contact)
  151. 535807b 2026-06-15 MCC polish: publish-time caveats for half-wired channels (TikTok private-only, YouTube uploads-not-wired) on chips + confirm dialog, created date+time + sort on Channels outbox, float CC import+ranked-opens above mock live cards
  152. 9968bde 2026-06-14 MCC should-fix sweep: unify Kravet block (lib/kravet.js, server+client single source, caption/name-aware, button disabled on block), add CA §17529.5 subject/from screening, origin-absolute fetches in assets+CC panels, align OAUTH_REDIRECT_BASE default, add 5th LinkedIn template
  153. da76726 2026-06-14 MCC officer-review fixes: fail-closed auth (remove hardcoded password default), HttpOnly/Secure cookie, gitignore runtime-state files, fix Wallpaper->Wallcovering leak
  154. 4715bb6 2026-06-14 MCC QA fixes: re-render #3 (Anemone Blush, was off-theme) + #5 (statement mural, was tiled); soften mural copy to drop false 'no repeats' claim
  155. 2a5b559 2026-06-14 Channels: bake the Meta 4-step setup doc into the Meta card (Ads-app gotcha + Other/Business → Instagram+FB Login products → App Review Standard Access → then mint token); Meta parked
  156. 744335b 2026-06-14 MCC: approve + schedule the 8 launch social posts (Bold Blush 6/15-16, Custom Mural 6/17-18); LinkedIn drafts marked ready (manual)
  157. e0f2be0 2026-06-14 MCC: swap in AI room-renders for the 8 launch drafts (Gemini 2.5 flash image, hosted on product galleries); drafts stay unapproved/unscheduled
  158. eaee696 2026-06-14 MCC: attach on-theme product imagery to the 8 staged Bold Blush + Custom Mural drafts (Shopify CDN URLs, Meta-fetchable)
  159. 5d49b72 2026-06-14 MCC: stage Bold Blush + Custom Mural launch posts — 8 social drafts (IG/Pinterest/TikTok/FB) + 2 LinkedIn trade drafts, all unapproved/unscheduled (scheduler-safe), awaiting imagery + Meta token
  160. 54fd8bb 2026-06-13 CC Phase 1 (DTD verdict A): CSV import (contacts/campaigns/opens, no token) + ranked opens (campaigns by open-rate, most-engaged contacts) — normalized ctct-store, same shape the live v3 API will write
  161. 0c63f9f 2026-06-13 Image Bank: finish Repost UI — gated modal (image preview, required @credit, rights-confirm + #ad, wallpaper-ban) → stages via /repost, never auto-posts
  162. 923cb9c 2026-06-13 LinkedIn module (DTD verdict A): draft-first composer w/ DW B2B templates + char counter + wallpaper word-ban + copy/deep-link manual post + saved drafts; gated official-API publish (never auto-fires, Phase 2 token) — nav under Social Media
  163. c04d2b7 2026-06-13 Image Bank: gated STAGE-ONLY /repost endpoint (rights-confirm + Kravet-block + auto-credit + wallpaper-word-ban + #ad) + /reposts list — UI button pending
  164. 961a1bb 2026-06-13 Image Bank: Instagram ingest — public-permalink og:image grab (no token, tagged uncleared) + our/vendor IG roster (33 accounts) with regram allow/review/block (Kravet-family blocked) + instagram source filter
  165. 267a4bb 2026-06-13 Asset Library = Image Bank: add GDrive (folder rclone-import + file share-link), Harvest-from-campaign-HTML, Constant Contact library (Phase 2 gated) ingesters + sort + source filter
  166. bdfc352 2026-06-13 Channels: self-serve Activate page — paste app creds in-browser (written to .env, never echoed), copy redirect URIs, portal links, one-click Connect; /setup + guarded /config endpoints
  167. 2b26633 2026-06-13 MCC shell: fetch against location.origin so embedded-URL credentials don't throw 'URL includes credentials'
  168. c79449d 2026-06-13 MCC shell: fix auth-cookie so fetch() works (was hanging on Loading), grouped hamburger nav w/ Social Media leading, hardened boot + error states
  169. c6baf4f 2026-06-12 social: add validated meta-go-live.sh (Graph-verify token → deploy to Kamatera → confirm backend:meta); gitignore meta-creds.env
  170. 0f9d596 2026-06-12 social: add free Meta Graph (IG+FB) publish backend behind ayrsharePost abstraction
  171. 751cc47 2026-06-12 gitignore transient social-publish-log.json
  172. cfea2d7 2026-06-12 social: Ayrshare live-publish + scheduler scaffold (gated, off by default)
  173. a905372 2026-06-10 backsync: pull Yuri's 10 CRM features from prod (RFM, send-times, ab-tests, profiles, segment-perf, templates, journeys, browse-abandon, copy-scoring, playbook) — Mac2 source now matches live 18-panel build
  174. 57a572a 2026-06-10 vendors module: prefer headless-verified follower counts + verifiedLive flag; BN Walls recheck
  175. 1cc1006 2026-06-10 Vendor IG: headless-verified follower counts (33 live, real numbers) — Rebel Walls 361K, Phillip Jeffries 527K, exact small-account figures
  176. a57c5b8 2026-06-10 Channels OAuth connect machinery: per-platform connect/callback (YouTube/Google, TikTok, Meta FB+IG) + token store + one-click Connect buttons; reads app client creds from env
  177. b546fbd 2026-06-10 Copy panel: Gemini path (preferred over dead Anthropic) → real AI copy via valid Gemini key
  178. a85f5b2 2026-06-10 Channels publishing engine: FB/IG/TikTok/YouTube adapters (real Graph/TikTok API behind credential gates) + Connections+Composer panel + staged outbox until accounts authorized. The posting-machine spine.
  179. dc913ce 2026-06-10 Vendor IG Reporting panel: DW + 37 vendor Instagram accounts roster (33 found, ~4.5M combined reach), sortable, for social/competitive reporting
  180. d54875f 2026-06-10 3 more panels (parallel agents): Performance dashboard (KPIs+SVG charts+GA4 path), Social Scheduler (queue board, gated publish), Audience Segments (RFM-ready rule builder) — 8 panels total
  181. 7207cc9 2026-06-10 assets: full-catalog search via Shopify predictive search (suggest.json)
  182. 0696349 2026-06-10 assets: image Asset Library module (upload / URL / live DW-catalog pull) + Layouts picker
  183. 0f49e0b 2026-06-10 load .env file in server.js so PORT/creds come from file (fixes prod EADDRINUSE on default 9661)
  184. 4b6184a 2026-06-10 deploy config → port 9662 (9660/9661 taken on Kamatera)
  185. e8363ce 2026-06-10 4 panels built (parallel agents): Constant Contact (mock+gated sends), Marketing Calendar, Suggested Copy, On-Demand Layouts — all mount + smoke-tested
  186. de56576 2026-06-10 scaffold: modular marketing command center shell (express, basic-auth, panel loader, module contract)

Authors

Agents used

  • none detected

Skills used

  • /channels6
  • /publish6
  • /data5
  • /clients-manifest5
  • /fbcdn4
  • /cache4
  • /pages4
  • /panels4
  • /assets4
  • /ig-repull-status3
  • /media3
  • /app3
  • /clients-notes3
  • /catch3
  • /price3
  • /color3
  • /style3
  • /book3
  • /hue3
  • /status3
  • /upload2
  • /com2
  • /media-cache2
  • /outbox2
  • /config2
  • /composer2
  • /reels2
  • /index2
  • /follow2
  • /state2

Creative ideas + design notes

Commits with substantial prose (≥120 chars) — the rationale behind each move.

39fcdef · 2026-07-27 · MCC bluesky: fail loud if image can't embed — never silently post text-only
postBluesky() previously swallowed any image fetch/uploadBlob failure and posted
text-only anyway (embed left undefined), which slips past the route-level text-only
guard (content.mediaUrl was truthy upstream) and violates Steve's 2026-07-17
'DW never posts text-only' rule — silently, on ok:true. Now aborts the post with a
loud error on any fetch/upload failure. Found by /contrarian gating TK-8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5d1c41b · 2026-07-22 · chore(mcc): move IG re-pull schedule from Mac2 launchd to Kamatera-local cron
The re-pull's script AND data both live on Kamatera, so a Mac2 launchd job SSHing
in was a needless cross-machine dependency (Mac2 asleep / SSH hiccup at 06:20 would
silently skip it — the exact drift the job exists to prevent). Now a self-contained
root crontab entry on Kamatera (06:40 daily). Removed the Mac2 wrapper + plist;
added scripts/ig-repull-SCHEDULE.md documenting the cron. Verified: cron present,
a manual Kamatera run wrote a fresh healthy status, Mac2 job booted out + gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21be5cc · 2026-07-22 · feat(mcc): board shows IG re-pull health at a glance + Run-now fix button
The daily IG media re-pull (com.steve.mcc-ig-repull) had no surface — you couldn't
see if it was running or stale. Now the Streams Board shows a health strip above
the columns: green/amber/red dot + last-run time + result, and a "Run now" button.

- ig-media-repull.js: writes data/ig-repull-status.json every run (ranAt/mode/
  recovered/graphErr/downloadDead/unrecoverable/ok).
- channels: GET /api/channels/ig-repull-status (reads it) + POST /api/channels/
  ig-repull-run (triggers --apply as a guarded child; 409 if already running).
- board.js: #bd-repull-health strip — amber if never-run or stale (>36h), red on
  graph/download errors, green when clean; "Run now" POSTs then refreshes.

Verified locally: status write, GET, POST trigger, concurrent-run 409 guard, and
the strip renders (green dot, timestamp, Run-now button).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6d2f55b · 2026-07-22 · chore(mcc): daily launchd job for the IG media re-pull (Mac2 → Kamatera SSH)
scripts/ig-repull-cron.sh: wrapper the LaunchAgent runs — SSHes to Kamatera and
runs ig-media-repull.js --apply, logging to ~/.claude/logs/mcc-ig-repull.log.
scripts/com.steve.mcc-ig-repull.plist: reference copy of the installed LaunchAgent
(runs daily 06:20; the live copy lives in ~/Library/LaunchAgents). Matches the DW
scheduled-ops pattern (launchd on Mac2 SSHing to Kamatera, like the canaries).
Bootstrapped + kick-tested end-to-end: SSH → --apply → exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
214b1bb · 2026-07-22 · fix(mcc): IG re-pull only touches at-risk images, never clobbers stable URLs
The re-pull was over-eager — it would refresh a published-IG post even when its
mediaUrl is already a durable cdn.shopify.com image, downgrading a full-res photo
to a square IG thumbnail. Now it only targets items whose current mediaUrl is
missing or an expiring ig/fbcdn signed URL; stable non-IG hosts are left alone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
971082a · 2026-07-22 · feat(mcc): IG Graph API re-pull to restore durable thumbnails for published IG posts
scripts/ig-media-repull.js: for every outbox item successfully published to
Instagram (detail[].id = media id, detail[].igId = business account), fetch the
media object's CURRENT media_url/thumbnail_url from Graph v23.0 — a fresh live URL
— and cache the bytes locally keyed by the STABLE media id, then rewrite the
item's mediaUrl to the /cache/media path. Makes published-IG thumbnails durable
regardless of future signed-URL rotation. Token resolved per-igId from
meta-pages.json (per-Page token), falling back to META_ACCESS_TOKEN/IG_ACCESS_TOKEN.

Dry-run by default (read-only Graph calls + report); --apply caches + rewrites the
outbox after backing it up. Items with a dead IG url but NO media id (failed
cross-posts) are reported unrecoverable — the placeholder tile is correct for those.

lib/media-cache.js: factor out fetchImageBuffer + writeAtomic; add exported
cacheAs(url, keyBase) for explicit-key (media-id) caching used by the re-pull.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2ffb36f · 2026-07-22 · fix(mcc): durable local cache for expiring IG/fbcdn post images + graceful placeholder
Board post thumbnails embed raw Instagram/fbcdn CDN URLs whose _nc_ohc/oh/oe
signature is time-boxed; once it expires the URL 403s for everyone (verified:
even a bare server-side fetch 403s), so the <img> failed and the old
onerror hid it -> blank tile ("posts all blank").

- lib/media-cache.js: localize() maps an IG/fbcdn URL to /cache/media/<sha1(path)>.<ext>,
  serving a cached local copy if present and background-downloading (best-effort,
  6s timeout, SSRF host-allowlist, atomic write) any missing one so the next load
  self-heals. Cache key is the media PATH (not the rotating signed query) so one
  media = one file. Never blocks the response.
- channels /api/outbox: localizeItems() the outbox before serving.
- board.js: on img error, swap to an inline branded "image unavailable" SVG tile
  instead of hiding — a dead/uncached image now reads as intentional, not blank.
- .gitignore: public/cache/ (locally-cached media, not source).

Verified end-to-end: outbox GET returns the localized /cache/media path and the
file serves 200; download pipeline writes valid image bytes. Deploy held (gated).
Note: images whose URL already expired before caching can't be recovered here —
fully restoring those needs a re-pull of fresh media from the IG Graph API (follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
243d9c5 · 2026-07-22 · fix(mcc): guard panel-router race that blanked panels + untrack screen-record videos
route() in public/app.js had no concurrency guard across its two awaits (fetch
panel HTML, load panel JS), so rapid panel switching interleaved two route()
calls that both wrote the shared #panel — a stale one landed after the fresh
render and wiped it (title stays, zero cards/API/errors = "posts all blank").
Added a latest-wins token: after each await, bail if a newer nav superseded.
Verified via /screenrecord (5-run order-varied recording, run3 reproduced the
all-blank cascade) + a fixed-app.js-over-live-data proxy A/B. Deploy held (gated).

Also .gitignore screenrecord/rec/ + *.webm and untrack the 10 committed webm
recordings (7.5MB) an auto-save hook had pulled into history going forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
29d1ff2 · 2026-07-22 · channels: /publish skips non-postable + media-mismatched channels
A full-fan-out publish used to FIRE every connected channel and collect
guaranteed errors (FB App-Review #200, LinkedIn 401, TikTok/YouTube image-as-
video). Now /publish consults the postable signal + media-type fit and SKIPS
(stages with a clear reason) any channel that can't actually take this post,
firing only the ones that will land. Result rows carry skipped:true + reason.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69af9fe · 2026-07-22 · channels: add honest 'postable' status — connected != can-actually-post
The status endpoint reported connected:true for channels that cannot post
(Facebook needs pages_manage_posts App Review; LinkedIn token is unscoped/dead;
TikTok/YouTube are video-only), which misled callers twice. Added a POSTABILITY
map encoding the known structural blocks and surface per channel:
  postable     — connected AND not structurally blocked (the real signal)
  mediaTypes   — what it accepts (image caller must check; TikTok/YouTube = video-only)
  postableNote — the why (App Review / dead token / video-only)
Also corrected the stale Threads caveat (tester-mode posts live without App Review).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d84dc2a · 2026-07-22 · engine: untrack runtime engine-queue.json + protect it on deploy
engine-queue.json holds live approve/arm/post status mutated only by the running
MCC — it was git-tracked (a latent trap: every deploy/commit carried a stale
snapshot). Untracked it + gitignored (its sibling engine-config.json already is),
and added it + engine-config.json to .deploy.conf RSYNC_EXTRA_EXCLUDES so a
--delete rsync can never clobber the live queue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d9e22d3 · 2026-07-22 · engine: image-grounded captions (vision) + always include DW SKU
Two changes to daily post-suggestion generation:

1. VISION-GROUNDED COPY — captionsFor() now runs a vision pass (qwen2.5vl:7b)
   on the actual product image first, producing a factual visual brief (motif,
   scale, color, texture) that grounds the qwen3:14b copy. Old captions were
   text-only from title/tags and read generic; new ones describe what the image
   actually shows. Graceful: image fetch / vision failure (incl. non-Mac hosts
   that can't reach ollama) resolves to '' and captions fall back to text-only.
   One vision call per product (not per channel). Source label gains '+vision'.

2. ALWAYS INCLUDE THE DW SKU — candidates.resolveSku() fills dwSku from
   dw_sku || sku || variant_sku (strips the '-Sample' suffix; dw_sku is often
   empty on the mirror). llm.withSku() then guarantees the SKU in every caption,
   sized so channel limits never truncate it, and strips any dangling '· SKU'
   tail when no code exists. SYSTEM/userPrompt updated to request it (belt) with
   withSku as the deterministic backstop (suspenders).

Verified end-to-end on the real Onda image: vision read the wave motif, all 7
channels carried '· SKU DWTT-73419' within limits; empty-SKU + broken-image
edge cases degrade cleanly. $0 (local ollama).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2545273 · 2026-07-22 · engine: stop birth-gating Threads suggestions (DW is a Meta tester, publishes without App Review)
Threads posts live in tester mode (proven: postThreads() succeeded this batch),
but generate.js was stamping every new Threads item status:gated from the static
GATED_CHANNELS map. Removed threads from that map so items are born 'suggested'
and approvable; the per-post approve->schedule step is still the real publish gate.
TikTok stays gated (genuinely SELF_ONLY until app audit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5ea8d24 · 2026-07-20 · calendars: filter Schumacher (internal-only) out of both calendar layers at source
Schumacher is internal-only and must never surface on a front end. The All
Calendars feed re-projects from dw_unified every ~10min, so scrubbing the JSON
alone would be undone on the next regen. Added a HIDDEN_VENDORS source filter
applied to the activation layer (after date projection, so other vendors keep
their real go-live slots), the launched layer, and the snapshot-fallback path.
Verified: live regen yields 0 Schumacher (activation 20304 -> 20034).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97dcfe7 · 2026-07-20 · Composer: add reel/story asset picker + Facebook Page selector
- /api/composer/reels serves generated reels as selectable video post media
- Reels tab (portrait video tiles) → sets videoUrl; video preview in composer
- Facebook Page picker (reuses /api/channels/pages) shown when FB is targeted
- postFacebook/postInstagram now publish video (FB /videos, IG REELS) + selected pages
- publish body carries videoUrl + pages; verified headless (25 reels, 80 pages, 0 errors)
b69f993 · 2026-07-17 · MCC Clients: server-side notes store so notes sync across devices
- /api/clients-notes GET+POST in server.js (atomic JSON store at
  data/clients-notes.json), mounted directly (not via registry) so no phantom
  nav panel; behind the existing basic-auth
- clients.js: pull+merge server notes on group open (server wins, local-only
  notes pushed up), debounced POST on each edit; localStorage stays the instant
  cache. Textarea hints 'syncs across devices'
- protect data/clients-notes.json from rsync --delete on deploy (.deploy.conf
  RSYNC_EXTRA_EXCLUDES + .gitignore)
2aac093 · 2026-07-17 · MCC Clients: per-contact notes field below each firm card
- auto-saving, auto-growing textarea under each broker/listing card, persisted
  per group in localStorage (keyed by the same recId as contacted/actions)
- gold 'has-note' highlight when a note exists; notes included in CSV export
30afe18 · 2026-07-17 · MCC Clients: show full address + phone + real domain + best contact/follow per firm
- backfill address/state/category/mapsUrl from the source Google-Places CSVs
  into the staged prospect JSONs (scripts/backfill-address-from-csv.py), with a
  cross-CSV host fallback for the group whose source CSV is absent
- clients.js: render 📍 full address (Google Maps link), 📞 tap-to-call phone
  chip, replace generic 'site ↗' with the actual domain (bsbdesign.com ↗), and
  a ⭐ Best contact/follow recommendation (gold-rings the recommended channel)
- carry the richer location fields forward in crawl-csv-emails.py so future
  re-crawls keep them; export CSV gains Address/State/Category/Best-contact cols
25362f4 · 2026-07-16 · chore: lint, refactor, v1.3.0 (session close)
Cosmetic-only cleanup of modules/calendars/lib.js (googleEvents stub params
uncommented to match call site; split a lone double-declaration). No behavior
change — smoke test green (activation+launched live, counts intact). Minor bump
reflects this session's launched-layer-live-on-Kamatera capability.
e0ae6f5 · 2026-07-16 · clients: browser+local-model contact enrichment (direct Instagram/LinkedIn + email gap-fill)
- enrich-contacts-browser.mjs: headless Chromium (Playwright) renders JS-only
  sites the HTTP crawler misses; pulls direct instagram/linkedin/tel from the
  DOM + mailto; falls back to local Ollama (qwen2.5) to deobfuscate emails. $0.
- Panel shows DIRECT IG/LinkedIn (gold ↗) when resolved, search (⌕) otherwise;
  CSV export carries the direct links. Wired into refresh-clients.sh weekly.
87dc441 · 2026-07-16 · calendars: decouple launched layer so it stays live where bulk_fivefield_worklist is absent (Kamatera)
Give the worklist (RR_SQL) query its own inner try/catch inside computeActivation
so a missing bulk_fivefield_worklist (Mac-only table) no longer sinks the launched
layer, which reads only shopify_products. When the worklist is absent, activation
items come from the last-good snapshot while launched recomputes LIVE. Adds
activation_live/launched_live flags; keeps top-level error null in that normal case
so the 'Live catalog unavailable' banner stops mis-firing.
0ed9d95 · 2026-07-16 · clients panel: FM client DB (43k) + CSV prospect groups, LinkedIn+Instagram discovery
- Clients & Prospects panel (Audience nav): per-group tabs, lazy-load, name
  filter, email + website + LinkedIn(all-search) + IG(google) per contact,
  contacted tracking, 300-row cap. Reads data/clients-manifest.json.
- crawl-csv-emails.py enriches Google-Places CSVs for contact emails locally
  (~70% pilot hit); build-clients-manifest.js scans staged data.
- Fleet-managed via refresh-clients.sh + com.steve.mcc-clients-refresh (weekly).
  Big datasets gitignored (regenerated + rsynced). NOT uploaded to CC (gated).
bfbe222 · 2026-07-16 · followlist: add build:followlist refresh script + CRCP broker section
- scripts/build-followlist.js regenerates linkedin-followlist.json from live
  sources (li-clicklist.html + CRCP brokers-snapshot.json), falls back to the
  committed JSON where a source is absent (e.g. Kamatera). npm run build:followlist.
- Adds 2,425 CRE brokers (deduped) as their own section; each is a LinkedIn
  name-search link (scraped linkedin field is an unreliable domain-guess).
- Panel: renders 'search' + firm sub-line, caps big sections at 300 rows with a
  filter-to-narrow note, and updates checkmarks in place (no 2.6k-row rebuild).
e347c43 · 2026-07-16 · linkedin panel: add Follow list mode (ported from li-clicklist.html)
New segmented toggle in the LinkedIn panel: Compose a post | Follow list.
Follow list is data-driven from public/data/linkedin-followlist.json (179
people across Hospitality/New-leads/Warm sections + 8 saved-searches),
baked into the project so it deploys to Kamatera (the ~/li-*.json source
files only exist on the Mac). Per-person 'followed' checkmarks persist to
localStorage with a progress bar + name filter, mirroring the :9819 hub.
063dc28 · 2026-07-16 · All Calendars: add Launched layer — already-live products on their launch date, own modal, v1.2.0
New 4th layer 'launched' (slate #3f5a73): products ACTIVE + online_store_published with
created_at_shopify in the last 30 days, placed on their real launch date (past-side companion
to the future-side staged/activation layer). Reuses the same enrichment via new shared
fieldsFrom() helper — carries vendor/material/price/color/style/book/hue, so the left-panel
facets filter it too. Launched chips are NOT draggable (historical) and open their OWN modal
(live product, Launched date, Shopify Admin + View-on-site links, no reschedule).

Verified local: 2,570 launched items over the window, render as live-bordered chips on June
days, legend toggles the layer, launched modal correct (tag/no-reschedule/live-links), 0 console
errors across the render. Ships live on the next (gated) MCC deploy alongside the DB-env fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f2866e6 · 2026-07-16 · chore: harden calendars module (pool error log, load() try/catch, fieldvis merge), v1.1.0 (session close)
Report-only lint+refactor review at close — all syntax valid, SQL parameterization safe,
no critical issues. Applied 3 safe non-behavioral robustness fixes:
- lib.js: pool 'error' handler now logs instead of silently swallowing
- calendars.js: load() wrapped in try/catch with Retry banner (was: spinner-forever on fetch fail)
- calendars.js: fieldvis prefs now MERGE over defaults (was: JSON.parse('') threw every fresh load)
Deferred cosmetic items (pad dedup, named SLOT_MIN constant client-side) — noted, not applied.
NOTE: local now 1 commit ahead of live; ships on the next (gated) MCC deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a83ad8f · 2026-07-15 · Place All Calendars in the Planning nav group (next to Marketing Calendar)
One-line app.js edit — Steve-authorized exception to the module-contract 'don't touch
app.js' rule so the panel groups with the existing calendar instead of falling into More.
c3e1ea5 · 2026-07-15 · All Calendars: front-end panel (facet rail + multi-layer month grid + drag reschedule)
public/panels/calendars.{html,js} — scoped under #cwroot (MCC style.css also defines
.grid/.card/.chip). Ports the astek left-panel field-toggle rail (search + type-ahead +
one collapsed table per field: vendor/material/price/color/style/book/hue/build-step/
channel/status/month, click-to-filter + count) and the dw-activation-calendar month grid
(image chips + slot times + drag-to-reschedule + detail modal), restyled to MCC's luxe
theme. Legend toggles each layer. Density slider scales chip size + type.

Verified headless (chromium): 42 cells, 8 facet tables, 12.3k chips render, vendor filter
20,356→4,394, zero console errors. Gitignores the 13MB regenerable snapshot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fea8d92 · 2026-07-15 · All Calendars module: backend (activation projection + shopify enrichment + marketing/campaign overlays)
New self-contained MCC module 'calendars' (📆 All Calendars) that merges 3 layers:
- activation: 20,304 staged SKUs on durable go-live dates (rr projection ported from
  dw-activation-calendar/generate-schedule.js), enriched from shopify_products so each
  SKU carries material/price/color/style/book/hue for left-panel field toggles
- marketing + campaign: reused from the existing calendar module via new shared
  modules/calendar/store.js (datesForMonth + campaign upsert — one source of truth)
- google: fail-soft stub (returns [] until OAuth is wired, Steve-gated)

Endpoints /events (?fresh=1) /refresh /move (drag reschedule → overrides file).
Fail-soft: DB unreachable → mock mode, never crashes the shell. Adds pg dep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
f2636e3 · 2026-07-15 · channels: Instagram-account selector — pick 1-to-all IG accounts
When only Instagram is ticked, the page picker reframes as 'Instagram accounts':
rows show @handle first, All/None act on the visible (IG-filtered) list so All =
all IG accounts, count reads 'N of M Instagram accounts', search matches @handle.
Verified via Playwright (label/rows/All/None, 0 console errors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90978c9 · 2026-06-24 · Add Followers/Following tab to DW Marketing Command Center
New follow-counts module: per-DW-IG-account followers_count + follows_count
(COUNTS ONLY — official Instagram Graph API returns no username lists) with a
once-daily snapshot store for growth-over-time charting.

- modules/follow-counts/index.js: /accounts, /history, /snapshot, /roster.
  Pulls counts via Norma instagram-agent (:9810) — reuses its IG token/IG_USER_ID,
  honors simulation mode (labels 'awaiting live IG creds', never fakes live).
  Daily-idempotent JSON snapshot store (data/follow-counts-history.json).
- public/panels/follow-counts.{html,js}: per-account count cards + delta +
  dependency-free SVG growth chart (followers/following toggle).
- registry.js + app.js: mounted under Analytics nav group.
- scripts/follow-counts-snapshot.js + deploy/com.steve.dw-follow-counts.plist:
  STAGED once-daily launchd capture (not loaded — gated on Steve).
- .env.example: NORMA_IG_* override docs + go-live note.

Surface chosen by DTD (3/3 unanimous): marketing-command-center.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
eac09ca · 2026-06-24 · CC module: proxy Connie cc-agent when no local CTCT token (DTD verdict C)
- /campaigns and /calendar now try the live Connie cc-agent (env CC_AGENT_*,
  else gitignored .cc-agent.json, else defaults) before falling back to mock
- scoped self-signed-TLS https request (rejectUnauthorized:false per-request,
  never process-wide), 6s timeout so a DOWN agent degrades to mock cleanly
- response carries source: ctct | connie | mock + fallback_reason on degrade
- send/draft/schedule gates untouched (CAN-SPAM / CA-17529.5 / confirm)
- .cc-agent.json gitignored; .env.example documents CC_AGENT_* vars

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
88a51a7 · 2026-06-22 · Social: real approval pipeline + instant posting + truthful live/simulated UX
Audit found posts were 'simulated' only because no social backend is connected
(META token is a placeholder + expired; flags off) — the code can post for real.

- backend: pipeline statuses (drafted→pending-approval→approved→posted), new
  /submit (Submit for approval) + /approve (Steve sign-off) + /status (live vs
  simulated truth) endpoints. /publish already does real Meta/Ayrshare posting +
  marks posted; instant = inline approve+confirm.
- panel: board columns = the 4 pipeline stages; per-card action by status
  (Submit for approval → Sign off ✓ → Publish); ⚡ Instant post button on the form;
  banner now reads ● LIVE (backend connected) or ● SIMULATED from /status, with
  a real ⚠️ 'posts to the REAL account' confirm when live.

Verified end-to-end on an isolated port: draft→submit→approve→publish stages
correctly (no backend), instant path works. Goes LIVE the moment a valid Meta
token + META_IG_USER_ID land (the only remaining blocker).
fe2afd8 · 2026-06-17 · meta-token-health: fix dead George alert URL (HTTPS flip 2026-06-17)
http://100.107.67.67:9850 now 400s; use the Tailscale MagicDNS host + raw
GEORGE_AUTH + body field (matching the working watchdog sender). The token
itself is dead ('Cannot parse access token') — rotation is Steve-gated. The
Vendor IG cache fallback already exists (panel renders from vendor-posts-cache).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5435736 · 2026-06-15 · Channels: add Facebook/Instagram Page picker — select WHICH of your 80 Pages (+ 11 linked IG) to post to.
- /api/channels/pages discovers all Pages via META_ACCESS_TOKEN (/me/accounts), caches per-Page tokens + IG links to data/meta-pages.json (gitignored); tokens stripped before sending to browser.
- postFacebook/postInstagram now post per-selected-Page using each Page's own token (legacy FB_PAGE_IDS path preserved as fallback).
- Composer: searchable Page picker (All/None/IG-linked/refresh), selection persisted to localStorage, shown when FB/IG targeted; publish guard requires >=1 Page.
- platformStatus marks FB/IG connected when META_ACCESS_TOKEN present.
Tested locally: /pages 200 (80 pages, 11 IG, no token leak), no-auth 401.
0f9d596 · 2026-06-12 · social: add free Meta Graph (IG+FB) publish backend behind ayrsharePost abstraction
- backendName() picks ayrshare (paid) > meta (free) > null (stage-only)
- metaPost(): IG 2-step container+publish, FB /photos|/feed, native Graph API
- publish() dispatcher routes /publish route + scheduler to active backend
- /connection reports backend + liveChannels; all confirm/approved gates intact
- .env.example documents META_ACCESS_TOKEN/IG_USER_ID/FB_PAGE_ID (free path)
cfea2d7 · 2026-06-12 · social: Ayrshare live-publish + scheduler scaffold (gated, off by default)
- ayrsharePost(): one-key publisher to IG/TikTok/Pinterest/FB; mock/stage when no key.
- /publish live branch now posts via Ayrshare ONLY when AYRSHARE_API_KEY set AND
  confirm:true AND post approved:true; otherwise stages, sends nothing (unchanged).
- scheduler: 60s tick, fires due+approved 'scheduled' posts — DRY unless
  SOCIAL_SCHEDULER_LIVE=true + key present; marks posted, logs every action.
- new posts carry approved:false; /connection + /publish-log surfaces; .env.example.
- verified: no key => staged only, unapproved => rejected, nothing sent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7207cc9 · 2026-06-10 · assets: full-catalog search via Shopify predictive search (suggest.json)
q= now hits /search/suggest.json (whole storefront) so deep lines like
grasscloth/silk/cork are findable; falls back to cached browse-filter on
rate-limit/non-JSON. normImg() handles protocol-relative cdn URLs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0696349 · 2026-06-10 · assets: image Asset Library module (upload / URL / live DW-catalog pull) + Layouts picker
- new modules/assets backend: list/upload(base64)/add-url/catalog/save-catalog/delete/file
- standalone Asset Library panel (public/panels/assets.*) — 3 add modes, created date+time chip, density slider
- Layouts panel gains a 📎 Assets picker drawer (Library + DW Catalog tabs) feeding the image field
- self-seeds 6 real DW catalog images; runtime data (data/assets*) gitignored so deploys don't clobber
- server.js JSON limit 2mb→28mb for image uploads; registry +assets

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

File tree

169 files tracked. Click any to browse the source at HEAD.

Other build journals

← Malden House  ·  all 4 projects  ·  Mdc Onboard →

Export

commits.csv · feed.atom · project.json · commits.json

rendered in 3ms