product-update-newsletter

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

Draft, update, or audit a crisp, changelog-grounded Anarlog product-update newsletter in Loops (app.loops.so) for a desktop release. Use after the changelog is merged, when asked to draft, revise, or pre-send check the release announcement email.

.agents/skills/product-update-newsletter/SKILL.md

Download bundle ↓
main · 143e3d82 bundle filesScanned 2026-09-15

SKILL.md

2,812 tokens · o200k_base · 12,059 bytes

Source excerpt starting at line 1.
---name: product-update-newsletterdescription: Draft, update, or audit a crisp, changelog-grounded Anarlog product-update newsletter in Loops (app.loops.so) for a desktop release. Use after the changelog is merged, when asked to draft, revise, or pre-send check the release announcement email.metadata:  internal: true--- # Product Update Newsletter Announce a desktop release to users through the "Anarlog update (...)" campaign in Loops. Run this after `../new-changelog/SKILL.md` produces the entry and the version is released via`../release-new-version/SKILL.md`. The published changelog is the copy source, not the raw commit log. ## Nightly announcement The newsletter follows stable releases. Do not send a newsletter for eachNightly. In the next stable update, include the opt-in Nightly announcementfrom [nightly-announcement.md](nightly-announcement.md), alongside that stablerelease's changelog. Existing users remain on stable automatically. Before describing Nightly as available, verify its published installers, updatefeed, and `https://anarlog.so/download/nightly/`. Use the released stablechangelog as the main source; Nightly-only changes are not shipped stablefeatures. Nightly release notes live in the app and GitHub prereleases, not thewebsite changelog. Prepare copy locally before publication is ready; create orupdate the Loops campaign once the stable release and linked Nightly are live. ## Source of Truth Read `https://anarlog.so/changelog/<version>`, which renders `packages/changelog/content/<version>.md`. The changelog is frequently edited after first publish. When told it changed, re-fetch and diffagainst the current email body, then apply only the delta. Do not reapply the whole rewrite. Map items to the existing structure rather than appending a dump: - Feature-level items become or extend an H3 section.- Minor UX items become bullets under "Small things that add up".- Reliability and privacy items append to the fixes paragraph after the bullets. Keep beta or partially shipped work honestly qualified. If execution is not connected yet, say so. ## Copy Style Contract Write a product email, not an essay. Keep it crisp, concrete, and easy to scan. - Open with what shipped and when. Prefer `We shipped four releases since last week. Anarlog  1.4.8 is here.` over scene-setting such as `If you've been putting off updating, this is the one  to take` or `It folds in four releases`.- If encouraging an update, use explicit loss framing tied to named features: `If you aren't  updating, you're missing out on Automations that run after every meeting, editable transcripts,  meeting imports, and near-instant sync.` Do not use vague hype, guilt, or blame.- Lead with outcomes and specific numbers: `30 assistants`, `eight connect directly`, `about a  second`. Cut adjectives when a fact can do the work.- Use short, active H3 headings such as `Automations do the work now`, `Transcripts you can fix`,  and `Sync you stop thinking about`.- Keep most paragraphs to one to three sentences. Split long chains, remove repeated setup, and do  not restate the same framing in both the intro and fixes paragraph.- Use contractions and direct verbs. `Last week ... This week ...` is a useful cadence when it  connects one update to the next. Avoid cutesy transitions such as `Now they earn it` and vague  phrases such as `Accuracy got quieter improvements`.- Keep subjects compact and noun-led: `Anarlog 1.4.8: automations, meeting imports, instant sync`.  Preview text should front-load concrete outcomes and stay near 90 to 140 characters.- Self-critical language belongs only when Anarlog or the company was actually at fault. Never add  an apology merely to sound personal.- Read the final copy aloud and cut anything that can disappear without losing a fact, instruction,  or point of view. Changelog lists are allowed; do not force prose just to avoid a list. ## Campaign Setup Duplicate the most recent "Anarlog update (...)" campaign from the Loops Home page so sender,reply-to, section headings, Download button, and footer are inherited, then rename to the new date. Campaign metrics pages show only send/open/click stats. Use the compose view to edit content. ## Body Structure Contract Every section is exactly this block sequence, and edits must preserve it: ```textH3 headingP (empty spacer)DIV.editor-imageP (empty spacer)P (body text)``` Verify before finishing: ```jsawait page.evaluate(() => {  const blocks = [...document.querySelector(".ContentEditable__root").children];  return blocks    .filter((b) => b.tagName === "H3")    .map((h) => {      const i = blocks.indexOf(h);      return (        h.innerText +        " -> " +        blocks          .slice(i + 1, i + 4)          .map((x) =>            x.className.includes("editor-image")              ? "IMG"              : x.innerText.trim() === ""                ? "SP"                : "TXT",          )          .join(",")      );    });});``` ## Editing the Lexical Body The body is `div.ContentEditable__root`. The page has several `[contenteditable="true"]` elements(Sender, From, Reply, Subject, Preview, body). Match on distinctive existing text, never a cachedindex or ref. Before any multi-block pass, rescan the full block map and resolve targets again. Theuser may edit concurrently, so a previously correct fixed index can shift and corrupt headings,bullets, or the greeting. `###` creates an H3 and `-` creates a bullet. Reliable mid-document replacement is Range-select then type: ```jsasync function selectTextInBody(searchStr) {  return await page.evaluate((str) => {    const root = document.querySelector(".ContentEditable__root");    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);    let node;    while ((node = walker.nextNode())) {      const idx = node.textContent.indexOf(str);      if (idx !== -1) {        root.focus();        const range = document.createRange();        range.setStart(node, idx);        range.setEnd(node, idx + str.length);        const sel = window.getSelection();        sel.removeAllRanges();        sel.addRange(range);        node.parentElement.scrollIntoView({ block: "center" });        return true;      }    }    return false;  }, searchStr);}``` Repeat per occurrence for multi-hit edits such as version bumps. ### Failure Modes These corrupt the document silently. All four were observed in practice. - Range selection on the **first block** reverses typed characters, so "Hi," becomes ",iH". Lexical  resets the caret to offset 0 per keystroke there. Use a native mouse triple-click, then type.- `Meta+ArrowUp` does not reliably reach document start. It has appended to the end of the current  block instead. Triple-click the target block.- Coordinates read in the same `evaluate()` as `scrollIntoView` are stale, so clicks land in the  wrong block and keystrokes corrupt unrelated sections. Scroll, `sleep(600)`, then re-read the rect  in a second `evaluate()`.- Pressing Enter with an active Range selection deletes the selected text. Press `End` or `Home` first. Verify caret placement before typing: ```jsconst ok = await page.evaluate(() => {  const sel = window.getSelection();  const blocks = [...document.querySelector(".ContentEditable__root").children];  const target = blocks[TARGET_INDEX];  const anchorBlock = sel.anchorNode    ? (sel.anchorNode.nodeType === 1        ? sel.anchorNode        : sel.anchorNode.parentElement      ).closest(".ContentEditable__root > *")    : null;  return anchorBlock === target;});``` If false, click the horizontal middle of the block's text rather than its left edge. After any structural repair, scan all blocks for stray empty paragraphs, duplicated greetings, andleftover `/` or `/image` text. ## Inserting an Image The slash menu triggers a native file chooser. Arm the listener before opening the menu. ```jsconst fcPromise = page  .waitForEvent("filechooser", { timeout: 15000 })  .catch(() => null);await page.keyboard.type("/", { delay: 200 });await sleep(700);await page.keyboard.type("image", { delay: 120 });await sleep(700);const btn = await page.evaluate(() => {  const menu = [...document.querySelectorAll("div.fixed")].find((el) =>    el.className.toString().includes("z-[100]"),  );  const img =    menu &&    [...menu.querySelectorAll("button")].find(      (b) => b.textContent.trim() === "Image",    );  const r = img.getBoundingClientRect();  return { cx: r.x + r.width / 2, cy: r.y + r.height / 2 };});await page.mouse.click(btn.cx, btn.cy);const fc = await fcPromise;await fc.setFiles("/abs/path/to/image.png");``` - The caret must sit in an empty paragraph, and `/` needs `delay >= 150` or a literal `/` is typed.- The menu is a scrollable `div.fixed.z-[100]`. Unfiltered, the Image item under MEDIA sits below the  visible scroll area, so clicking its reported rect hits the page behind and silently closes the  menu. Typing `image` filters the list and brings it into view.- `Enter` and `ArrowDown`+`Enter` do not activate the item. Only a mouse click works.- Poll until `img.src` contains `images.vialoops.com` to confirm the upload finished.- Insertion consumes the empty paragraph, so re-add the trailing spacer. Replace an image by clicking it to select the block, pressing Backspace, then re-running the flow.Capture existing `images.vialoops.com` URLs before editing so a deleted image can be re-downloadedand restored. ## Section Art Generate with Midjourney using the established house style: ```textdithered. [short concept]. bright white sunshine, cheerful, high key. --ar 4:3 --profile aofpoq2``` - Target bright, high-key, airy, light-background results. Reject dim or dystopian output.- Prefer scene compositions, an object in a sunlit room, over flat grids or UI mockups. Grids do not  match the existing set.- The Personalize "P" toggle may fail to activate, leaving `data-active="false"`. Appending  `--profile aofpoq2` to the prompt text works and produces the profile chip.- Poll for completion by watching for `\d+% Complete` to disappear from `document.body.innerText`. `cdn.midjourney.com` returns a Cloudflare challenge to Node-side `fetch()`. Download full resolutioninside the authenticated tab, where `jobId` comes from the grid thumbnail`cdn.midjourney.com/<jobId>/0_<n>_640_N.webp`: ```jsconst b64 = await mjPage.evaluate(async (u) => {  const r = await fetch(u);  const blob = await r.blob();  return await new Promise((res) => {    const fr = new FileReader();    fr.onload = () => res(fr.result.split(",")[1]);    fr.readAsDataURL(blob);  });}, `https://cdn.midjourney.com/${jobId}/0_${variant}.png`);``` ## Pre-Send Audit Run before sending and report pass or fail per item. Automated: - Version string consistent across subject and body, with no prior version anywhere.- Every changelog item is represented in the copy, including contributor  thanks when a change came from someone outside the Fastrepl org.- All H3 sections match the `SP,IMG,SP` pattern.- All images report `complete && naturalWidth > 0`.- No stray artifacts: `/image`, orphan `/`, double spaces, duplicated greeting.- Sign-off block intact. Manual judgment: - Sender, From, and Reply fields correct. Flag any reply-to change, since the body promises a  personal reply and any call for testers routes to the same address.- Preview text length. Clients truncate near 90 to 140 characters, so front-load the important content.- The Download button URL lives in editor state, not the DOM. Click the button block and read the  Link textbox in the right sidebar.- State explicitly that only the Compose step was audited unless Audience, Schedule, and Goals were  also checked. ## Reporting Restate what changed in plain language, quote the new copy, and attach a screenshot of editedsections. Disclose any self-inflicted document damage and its repair rather than hiding it. 
Discovery context

Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.