diff --git a/.ai-sessions/lessons.md b/.ai-sessions/lessons.md index 136d3a0..a266053 100644 --- a/.ai-sessions/lessons.md +++ b/.ai-sessions/lessons.md @@ -2,6 +2,8 @@ ## Recent +- Google Forms' new publish flow defaults the responder audience to the org; a forms.gle link returns 401 anonymously until Responders is set to "Anyone with the link". Always curl responder links unauthenticated before announcing them (2026-09-06) +- The meetup.com pro dashboard's network-events view does not show network events created via the API filterId path, so a dash-created event can silently duplicate a script-created one; check the origin group's ACTIVE events by networkEvent id when duplicates are suspected (2026-09-06) - `mkdocs build --strict` (and `just validate`) fail on this repo even on clean main because `site_url` is unset; CI deploys via `gh-deploy` without strict, so validate changes with a plain `mkdocs build` (2026-09-06) - On a rerun of the monthly update, audit artifact existence first (Mailchimp campaign list, Drive title search, Canva deck page-1 thumbnail, Discord scheduled-events API) instead of walking the skill's from-scratch order (2026-09-06) - A speaker headshot that arrives as a Google Drive link: download it via the Drive MCP and commit it to `docs/assets/images/` so `mkdocs build --strict` and CI are not blocked; the pushed branch's `raw.githubusercontent.com` URL then doubles as the public source for the Canva `upload-asset-from-url` (a private Drive file is not fetchable by Canva) (2026-08-14) @@ -10,8 +12,6 @@ - `str.format(**ctx)` does not re-parse braces inside the substituted values, so a data field like `"Understanding {} in Python"` renders literally and never raises; verify any "format-injection crash" claim empirically before fixing (2026-08-14) - Squash-merged branches look "not in main" by commit ancestry (`merge-base --is-ancestor` fails) because their commits were rewritten; confirm merged-vs-stale by content: a diff dominated by deletions that re-adds older text means main is ahead and the branch is stale (2026-08-14) - When resolving an add/add conflict in a sops-encrypted file, take the side that is the plaintext superset (its MAC already covers the full key set) and verify by actually running `sops --decrypt`; a hand-stitched ciphertext would fail MAC validation (2026-08-14) -- Canva share/export links 403 to link checkers (bot-blocked, not broken); add `canva.com` to `.lycheeignore` rather than editing the reference doc (2026-08-14) -- Canva export URLs are signed and expire within hours; for Discord announcements attach the PNG to the webhook message (`files[0]` multipart) so the image outlives the link, and state the link's expiry next to it (2026-07-31) ## Secrets - `sops set file '["KEY"]' '"value"'` adds a key to a sops dotenv without a decrypt/edit/re-encrypt round trip (2026-07-31) @@ -24,6 +24,10 @@ - Don't verify an API write by GETting it back from the same API; it echoes your write. If the system of record is a UI (Mailchimp builder), the UI is the only honest check (2026-07-31) - Mailchimp Marketing keys end in `-usNN`; Transactional (Mandrill) keys are a separate product and format. The Transactional MCP rejects Marketing keys with "Invalid API key" (2026-07-31) +## Canva +- Canva share/export links 403 to link checkers (bot-blocked, not broken); add `canva.com` to `.lycheeignore` rather than editing the reference doc (2026-08-14) +- Canva export URLs are signed and expire within hours; for Discord announcements attach the PNG to the webhook message (`files[0]` multipart) so the image outlives the link, and state the link's expiry next to it (2026-07-31) + ## Workflow - meetup.com shows "0 upcoming events" to WebFetch because listings render client-side; the raw HTML's `__NEXT_DATA__` Apollo cache has full event data. Use `scripts/scrape_local_meetups.py` in the meetup-update skill, and check for embedded JSON before declaring any Next.js site unscrapeable (2026-07-31) - The meetup-update skill's `references/newsletter.md` is the canonical newsletter playbook; follow it over memory or improvisation (2026-07-31) diff --git a/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md b/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md new file mode 100644 index 0000000..3f87598 --- /dev/null +++ b/.ai-sessions/session-20260906-1718-meetup-dedupe-resend.md @@ -0,0 +1,50 @@ +# Session Summary: Meetup.com Dedupe and Announcement Resend + +**Date**: 2026-09-06 +**Duration**: ~45 minutes (same conversation as the October wrap-up) +**Conversation Turns**: ~15 in this stretch +**Estimated Cost**: moderate (GraphQL inspection plus webhook resends) +**Model**: Claude Fable 5 + +## Key Actions + +- Diagnosed duplicate October meetup.com events: two full network events existed (script-created 316447876, network 342e398b; dashboard-created 316448124, network f4f79e3f), each fanned out to all five network groups +- Mason chose to keep the dashboard event; deleted 316447876 via the deleteEvent mutation and confirmed its public page 404s +- Added a Meetup.com event line to both announcement templates in `build_announcements.py` (new required field `meetup_event_url`) at Mason's request +- Resent both Discord announcements (Mason had deleted the originals) with the surviving meetup.com link, merged PR status, fresh card export, and a trimmed Still-manual list +- Earlier in the conversation: sent the initial marketing and organizers announcements after Mason published both forms (a form-settings 401 had to be fixed first) and posted the website-updated follow-up after PR #55 merged + +## Prompt Inventory + +| Prompt/Command | Action Taken | Outcome | +|---|---|---| +| "Were the discord messages sent?" | Explained the deliberate hold on unpublished form links | Mason published the forms | +| "Read the google doc back" | Verified run of show; caught both responder links returning 401 | Mason fixed form audience; links went public | +| "Fixed, try again" | Verified 200s, rebuilt payloads, sent both announcements | Delivered with card attached | +| "I merged the PR... send the link" | Verified deploy live, posted website-updated follow-up | Done | +| "I feel there's duplicates" | GraphQL inspection found two parallel network events | Mason picked the keeper | +| "Resend the entire message with the updated meetup link" | Deleted the duplicate, added template line, resent both | Done | + +## Efficiency Insights + +**What went well:** +- Verifying responder links with an unauthenticated fetch before sending caught the restricted-audience default that would have shipped a login wall to Discord + +**What could improve:** +- The duplicate came from creating the network event both via the script and the pro dashboard in the same cycle; the organizers message should state loudly that the meetup.com draft already exists and only needs publishing + +**Course corrections:** +- "I deleted the old one" turned out to mean the Discord messages, not the meetup event; both events were still live until verified and deleted explicitly + +## Process Improvements + +- Before sending announcements, verify every link in the payload anonymously, not just the form links + +## Observations + +- Google Forms' new publish flow defaults responders to the org audience; forms.gle links return 401 until Responders is set to "Anyone with the link" +- The pro dashboard's network-events view does not list network events created through the API filterId path, which is how the duplicate went unnoticed + +## Suggested Skills for Next Session + +- `meetup-update`: November cycle; the outreach drafts for Sridhar Irujolla and Kriti Faujdar may have replies to process by then diff --git a/.claude/skills/meetup-update/scripts/build_announcements.py b/.claude/skills/meetup-update/scripts/build_announcements.py index b82e3a8..1ec4c54 100755 --- a/.claude/skills/meetup-update/scripts/build_announcements.py +++ b/.claude/skills/meetup-update/scripts/build_announcements.py @@ -57,6 +57,7 @@ "discord_event_url", "website_pr_url", "website_pr_status", + "meetup_event_url", ) MARKETING_TEMPLATE = """\ @@ -69,6 +70,7 @@ * [Attendance form]({attendance_url}) * Questions: in chat tonight * [Discord event]({discord_event_url}) +* [Meetup.com event]({meetup_event_url}) * [RSVP]({rsvp_url})""" ORGANIZERS_TEMPLATE = """\ @@ -77,6 +79,7 @@ * [Run of Show]({run_of_show_url}) * Card: [{deck_name}, page {deck_page}]({deck_url}); image attached, or [download the PNG]({card_download_url}) (link expires {card_expiry}) * [Discord event]({discord_event_url}) +* [Meetup.com event]({meetup_event_url}) * [Attendance form]({attendance_url}) * [Website PR]({website_pr_url}) ({website_pr_status})""" diff --git a/secrets/meetup.sops.env b/secrets/meetup.sops.env index 5494625..99d0495 100644 --- a/secrets/meetup.sops.env +++ b/secrets/meetup.sops.env @@ -3,11 +3,11 @@ PYTEXAS_MEETUP_WEBHOOK=ENC[AES256_GCM,data:o0eUcyJ8464OqJwnwJwfkBshaliya/9s62tRi MAILCHIMP_API_KEY=ENC[AES256_GCM,data:iZo1IvkKpsrv7vAhtu3E3DqFuTGe+nR4eTcwKEpZ1byqWbGsAQ==,iv:tai6OvFB2i26xMpx5vRqdL5Z0Tv5fngcDzw6B1QkLlI=,tag:qmh1lqlndVPbWp/bnsDH6g==,type:str] MEETUP_API_KEY=ENC[AES256_GCM,data:+2sPa/R+VSOtOHioho9c2cKq/3BDA5NSVXA=,iv:SnJCta7ujkSqFDZqkaSsr11pEK253ESqY/U6ePqIs34=,tag:oIUmg68ZjbucVKh6OTYnpg==,type:str] MEETUP_API_SECRET=ENC[AES256_GCM,data:idZPq0DEvxAlCg38YQ4vHUSGIFI40zZNgDk=,iv:OtAFPf6bZVJt/hOP0JyIeG1TDLnA09HZRdXWuu321ro=,tag:BkAHzj5jr1zQP/5puOntxg==,type:str] -MEETUP_REFRESH_TOKEN=ENC[AES256_GCM,data:JqHX5fKxW3rSBYIH8s+wvpqMoAGZ93lSHxa3RpwnfbJ1fPgNnWOdoMNmmUm9p5Ox20nrJakmHJrZmFaR8TwvnzzLcKzbZXp7rMPDOZiVkwKVCXn9nQcHYuXFXQCAd1qiEdgy7RCOLkU7bUogyTUuJ9xjxjhTxUqipux7QaKXw9dT8MpCLgF+GszbWdVFJNvX41CS13U7+jwuWc28HlTc2UXyW+Hu9/v5LZrYYIxpYjdup6dS0Q+UAgzHimRl+ZO+ONJ+bqA+z6e1ePHz2xvO4B1Rjgnr+71Ldymbh2Iq5gvWi2vBiuFMqIsJ+Kzv75gzKZn5wmRmLSk8QZ78GSpANd7Ye1C33JZY/zWioWGcpkYO26X44ZyKNDoOTB5EquGyki7s8S2Sb/k33HuqXV/hvAKHtDv3z2I0P9GyPgVR32uMPWwSTM4Mv8ZQmWTz+HyDU+hXGGXa0aFo+FYxBrjAM8wftRARzFDrHBBk17RSwCjMQQ69JMpKd309ZJa546BKkZdH48+nOGV+1LgwnClJKIPk20J8b/e6yquEfIMrxXglN7lHQXe65vLaHx2K0+qVmJOtZ0MexIShPifDMqILMt23FWkhUm0ztjvPIs0sB+KML3TMMM2BZ6SOs/tYpTg08HRnoQ5UkPNTYKbgSmdLull/oaZL72nkGG6I8twWISOzqS8zrkRIJte01DclbcTECePrJwnCTZKJ4Iu+L97mKZbqN1+IjuO2egRWi3Yv4NftAMAoH3Sh684m/pjSNVHJ8No6iVTkx5Iay4MC4EvupTDZ57PQNbchYBHqxpxoeMq492z2j73+h0kO3IoSRgEUydwdVw==,iv:sinQpTmcUq61pqpcAOAz+qKfPq3YPqYGER2yjd4czKQ=,tag:6R0+dd6EJE+Wb8VoNTDkyg==,type:str] +MEETUP_REFRESH_TOKEN=ENC[AES256_GCM,data:V+JPfZpJxi8oJMWIH6HqNgoS1IfwHku8A3qJA8KPo6TKVa2aNd2ckVevOsDYZFnu6J84O131oFGqXNuXaoxBqPMUlvEs3Wg5tuQvFGEWcIxJayvjgwEF7E1csJTI2770epM637/R9yY2lclXTEijRSBETgLYWHybdJep4jsmJihjy8BR5hX8T6HsWqCeXkWavTf9DS5DKBCmiQ1+wvnDsCMmqAAAk/ocbtFo6+TvI86IbwVsK5zkj/vJEwKm969X6NZZYvdIWEuBq3NmO4RuqBEsd0osdK6ikpAZ+Ck3RlNBxZy+PK2T7sNXMxtrU1oIKlkJ4jsafjgvTt2RdIHTUv3JwWzcM3CRsZVFJOq63DV0cXntvB9o70/XpJ5ZssII28X3sLzY3sgFqUBQD3LEDhC+E+ikCg7u2P8586cVzqxPrRI/S5T+eV3sUTa7OQSfN+mFzBbDaq6cmppjCC1tGFTA0TCQEN76iHDrsQwqSQyav0NMQLPl/L0MaNHk5xP0oihtUOLH9HJrWEbFMz9TSKpBPmGUW2fDTKEc3N/lHmtGoLoBihzmnBEG7QiIhD/2WGPctf6OOeN+D1S6bKF3DOt29z3b0dbxlXXBRtUsDw3J29dTOTi4rwJZuVmcc6EbdSNiPr0MGSj2rP8I+FDXEEC27lER3Q55zksqx/I5KPm2IvVLg+9hkrgbisBbNgBsnkF1zosxUcxAKys2d+Cqr+tlapNeuOTx5UsK6xjHE/Y7iQLz+JrxAbE2/DIIGQWA1smvyeYF2xf24U2TcKcgB5Z+rHL+dyFrO+cqukNX0ZUdinKxyfGyW/CejYSGAN13bsYbhA==,iv:kveheAFQQDAkiDbhK5FYcj2nAcQaJbbtq6jnjejuLCg=,tag:Bdr+KOvT1LvI8A3hCxdgtg==,type:str] PYTEXAS_DISCORD_BOT_TOKEN=ENC[AES256_GCM,data:F0JvIvALzRUF6jrnkhtUPEKYb7HXdpKTEcg3sKY3Xkxf2L9H3i5OSKfgyfE6upWjNdPQKZu4jqJaxKw709Wvdg+pMNFfR69B,iv:tN2srDHO9bCJEW+tc7nl4zDbKkcf7a0DE01nw6Jmq6g=,tag:JERscJ5CEEbdyBC3A2WNBw==,type:str] sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvTlczR3puNFJOa2dJN0hS\ncVp5Tzk1K1ZHMDNIZFJIZG5WM0ordW00MWg4CjFCdWJvWmlDcmlkQ05zZFFqQlVR\nN3RHQzkrbnRteHZYbFM0NzJ3Si9qQlEKLS0tIHIxLzluZ3JCa0dSWG13N2dlYTVn\nMFpycUNHZ3p6SXpzdFRvL3VxeDlOUmsKvgfZuOwDCq+z/q+4m+TVNF2sMKpKZ4zs\nTQS17ua+xa6rgRAk2eG3PBp7GRKAhnGUV5WfA9++TU2O3CKmb6RGww==\n-----END AGE ENCRYPTED FILE-----\n sops_age__list_0__map_recipient=age17u5zkzgr4ca49sl9n42zk6aruq8d5xntnuxkhyjwvku80tx93afqm7a0fz -sops_lastmodified=2026-09-06T19:37:06Z -sops_mac=ENC[AES256_GCM,data:E1RSpTv75GzlAPj5mawz7qNNhwJu79M/ovrN3niV3qipgOIHsesvsXe1jpWod9JEOPNrXlbIk2C1V0jlL0Cn0WOBjFjMtqF7ES/lLeQ7BCAvtX7gEmf9NW9H8kSdROOKdZcsRJWP0Gb984mLEwxPirQ6oFP75hcga9/FQGPfNuo=,iv:i8O49khFw8Yd3kGj/88zS1YD3/D1JanBMMUHPC3WEfo=,tag:1bnoPDdVh39zXS6WiU98pA==,type:str] +sops_lastmodified=2026-09-06T22:17:11Z +sops_mac=ENC[AES256_GCM,data:qmjfb+lgwMd0glGOlFddayjeFk9kSL4YuftwI7BNkuFv241voqrGMNP3HPgrRkiRuD/LBtEqzySVP1/dPdVNzU7J+Ov+Yfw90TgoBfF6ZTeXpLfpGJaZykUlRsnP/hhTtoWxjj2bCHY2xK3zgIbh/lF4uOuJNmkOiYPs9rFoGlE=,iv:xcxeuN8DMEJ4Eq6GMvZkZrqsv0t+AtP1L+pzIbhCCIw=,tag:qkOShtLIYXwvUXhn8HrZ4g==,type:str] sops_unencrypted_suffix=_unencrypted sops_version=3.13.1