SKILL.md
SKILL.mdBrowse 2 files
2,812 tokens
12,059 bytes
Token encoding: o200k_base
Snapshot 143e3d8
1---2name: product-update-newsletter3description: 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.4metadata:5 internal: true6---7 8# Product Update Newsletter9 10Announce a desktop release to users through the "Anarlog update (...)" campaign in Loops.11 12Run this after `../new-changelog/SKILL.md` produces the entry and the version is released via13`../release-new-version/SKILL.md`. The published changelog is the copy source, not the raw commit log.14 15## Nightly announcement16 17The newsletter follows stable releases. Do not send a newsletter for each18Nightly. In the next stable update, include the opt-in Nightly announcement19from [nightly-announcement.md](nightly-announcement.md), alongside that stable20release's changelog. Existing users remain on stable automatically.21 22Before describing Nightly as available, verify its published installers, update23feed, and `https://anarlog.so/download/nightly/`. Use the released stable24changelog as the main source; Nightly-only changes are not shipped stable25features. Nightly release notes live in the app and GitHub prereleases, not the26website changelog. Prepare copy locally before publication is ready; create or27update the Loops campaign once the stable release and linked Nightly are live.28 29## Source of Truth30 31Read `https://anarlog.so/changelog/<version>`, which renders `packages/changelog/content/<version>.md`.32 33The changelog is frequently edited after first publish. When told it changed, re-fetch and diff34against the current email body, then apply only the delta. Do not reapply the whole rewrite.35 36Map items to the existing structure rather than appending a dump:37 38- Feature-level items become or extend an H3 section.39- Minor UX items become bullets under "Small things that add up".40- Reliability and privacy items append to the fixes paragraph after the bullets.41 42Keep beta or partially shipped work honestly qualified. If execution is not connected yet, say so.43 44## Copy Style Contract45 46Write a product email, not an essay. Keep it crisp, concrete, and easy to scan.47 48- Open with what shipped and when. Prefer `We shipped four releases since last week. Anarlog49 1.4.8 is here.` over scene-setting such as `If you've been putting off updating, this is the one50 to take` or `It folds in four releases`.51- If encouraging an update, use explicit loss framing tied to named features: `If you aren't52 updating, you're missing out on Automations that run after every meeting, editable transcripts,53 meeting imports, and near-instant sync.` Do not use vague hype, guilt, or blame.54- Lead with outcomes and specific numbers: `30 assistants`, `eight connect directly`, `about a55 second`. Cut adjectives when a fact can do the work.56- Use short, active H3 headings such as `Automations do the work now`, `Transcripts you can fix`,57 and `Sync you stop thinking about`.58- Keep most paragraphs to one to three sentences. Split long chains, remove repeated setup, and do59 not restate the same framing in both the intro and fixes paragraph.60- Use contractions and direct verbs. `Last week ... This week ...` is a useful cadence when it61 connects one update to the next. Avoid cutesy transitions such as `Now they earn it` and vague62 phrases such as `Accuracy got quieter improvements`.63- Keep subjects compact and noun-led: `Anarlog 1.4.8: automations, meeting imports, instant sync`.64 Preview text should front-load concrete outcomes and stay near 90 to 140 characters.65- Self-critical language belongs only when Anarlog or the company was actually at fault. Never add66 an apology merely to sound personal.67- Read the final copy aloud and cut anything that can disappear without losing a fact, instruction,68 or point of view. Changelog lists are allowed; do not force prose just to avoid a list.69 70## Campaign Setup71 72Duplicate the most recent "Anarlog update (...)" campaign from the Loops Home page so sender,73reply-to, section headings, Download button, and footer are inherited, then rename to the new date.74 75Campaign metrics pages show only send/open/click stats. Use the compose view to edit content.76 77## Body Structure Contract78 79Every section is exactly this block sequence, and edits must preserve it:80 81```text82H3 heading83P (empty spacer)84DIV.editor-image85P (empty spacer)86P (body text)87```88 89Verify before finishing:90 91```js92await page.evaluate(() => {93 const blocks = [...document.querySelector(".ContentEditable__root").children];94 return blocks95 .filter((b) => b.tagName === "H3")96 .map((h) => {97 const i = blocks.indexOf(h);98 return (99 h.innerText +100 " -> " +101 blocks102 .slice(i + 1, i + 4)103 .map((x) =>104 x.className.includes("editor-image")105 ? "IMG"106 : x.innerText.trim() === ""107 ? "SP"108 : "TXT",109 )110 .join(",")111 );112 });113});114```115 116## Editing the Lexical Body117 118The body is `div.ContentEditable__root`. The page has several `[contenteditable="true"]` elements119(Sender, From, Reply, Subject, Preview, body). Match on distinctive existing text, never a cached120index or ref. Before any multi-block pass, rescan the full block map and resolve targets again. The121user may edit concurrently, so a previously correct fixed index can shift and corrupt headings,122bullets, or the greeting. `###` creates an H3 and `-` creates a bullet.123 124Reliable mid-document replacement is Range-select then type:125 126```js127async function selectTextInBody(searchStr) {128 return await page.evaluate((str) => {129 const root = document.querySelector(".ContentEditable__root");130 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);131 let node;132 while ((node = walker.nextNode())) {133 const idx = node.textContent.indexOf(str);134 if (idx !== -1) {135 root.focus();136 const range = document.createRange();137 range.setStart(node, idx);138 range.setEnd(node, idx + str.length);139 const sel = window.getSelection();140 sel.removeAllRanges();141 sel.addRange(range);142 node.parentElement.scrollIntoView({ block: "center" });143 return true;144 }145 }146 return false;147 }, searchStr);148}149```150 151Repeat per occurrence for multi-hit edits such as version bumps.152 153### Failure Modes154 155These corrupt the document silently. All four were observed in practice.156 157- Range selection on the **first block** reverses typed characters, so "Hi," becomes ",iH". Lexical158 resets the caret to offset 0 per keystroke there. Use a native mouse triple-click, then type.159- `Meta+ArrowUp` does not reliably reach document start. It has appended to the end of the current160 block instead. Triple-click the target block.161- Coordinates read in the same `evaluate()` as `scrollIntoView` are stale, so clicks land in the162 wrong block and keystrokes corrupt unrelated sections. Scroll, `sleep(600)`, then re-read the rect163 in a second `evaluate()`.164- Pressing Enter with an active Range selection deletes the selected text. Press `End` or `Home` first.165 166Verify caret placement before typing:167 168```js169const ok = await page.evaluate(() => {170 const sel = window.getSelection();171 const blocks = [...document.querySelector(".ContentEditable__root").children];172 const target = blocks[TARGET_INDEX];173 const anchorBlock = sel.anchorNode174 ? (sel.anchorNode.nodeType === 1175 ? sel.anchorNode176 : sel.anchorNode.parentElement177 ).closest(".ContentEditable__root > *")178 : null;179 return anchorBlock === target;180});181```182 183If false, click the horizontal middle of the block's text rather than its left edge.184 185After any structural repair, scan all blocks for stray empty paragraphs, duplicated greetings, and186leftover `/` or `/image` text.187 188## Inserting an Image189 190The slash menu triggers a native file chooser. Arm the listener before opening the menu.191 192```js193const fcPromise = page194 .waitForEvent("filechooser", { timeout: 15000 })195 .catch(() => null);196await page.keyboard.type("/", { delay: 200 });197await sleep(700);198await page.keyboard.type("image", { delay: 120 });199await sleep(700);200const btn = await page.evaluate(() => {201 const menu = [...document.querySelectorAll("div.fixed")].find((el) =>202 el.className.toString().includes("z-[100]"),203 );204 const img =205 menu &&206 [...menu.querySelectorAll("button")].find(207 (b) => b.textContent.trim() === "Image",208 );209 const r = img.getBoundingClientRect();210 return { cx: r.x + r.width / 2, cy: r.y + r.height / 2 };211});212await page.mouse.click(btn.cx, btn.cy);213const fc = await fcPromise;214await fc.setFiles("/abs/path/to/image.png");215```216 217- The caret must sit in an empty paragraph, and `/` needs `delay >= 150` or a literal `/` is typed.218- The menu is a scrollable `div.fixed.z-[100]`. Unfiltered, the Image item under MEDIA sits below the219 visible scroll area, so clicking its reported rect hits the page behind and silently closes the220 menu. Typing `image` filters the list and brings it into view.221- `Enter` and `ArrowDown`+`Enter` do not activate the item. Only a mouse click works.222- Poll until `img.src` contains `images.vialoops.com` to confirm the upload finished.223- Insertion consumes the empty paragraph, so re-add the trailing spacer.224 225Replace an image by clicking it to select the block, pressing Backspace, then re-running the flow.226Capture existing `images.vialoops.com` URLs before editing so a deleted image can be re-downloaded227and restored.228 229## Section Art230 231Generate with Midjourney using the established house style:232 233```text234dithered. [short concept]. bright white sunshine, cheerful, high key. --ar 4:3 --profile aofpoq2235```236 237- Target bright, high-key, airy, light-background results. Reject dim or dystopian output.238- Prefer scene compositions, an object in a sunlit room, over flat grids or UI mockups. Grids do not239 match the existing set.240- The Personalize "P" toggle may fail to activate, leaving `data-active="false"`. Appending241 `--profile aofpoq2` to the prompt text works and produces the profile chip.242- Poll for completion by watching for `\d+% Complete` to disappear from `document.body.innerText`.243 244`cdn.midjourney.com` returns a Cloudflare challenge to Node-side `fetch()`. Download full resolution245inside the authenticated tab, where `jobId` comes from the grid thumbnail246`cdn.midjourney.com/<jobId>/0_<n>_640_N.webp`:247 248```js249const b64 = await mjPage.evaluate(async (u) => {250 const r = await fetch(u);251 const blob = await r.blob();252 return await new Promise((res) => {253 const fr = new FileReader();254 fr.onload = () => res(fr.result.split(",")[1]);255 fr.readAsDataURL(blob);256 });257}, `https://cdn.midjourney.com/${jobId}/0_${variant}.png`);258```259 260## Pre-Send Audit261 262Run before sending and report pass or fail per item.263 264Automated:265 266- Version string consistent across subject and body, with no prior version anywhere.267- Every changelog item is represented in the copy, including contributor268 thanks when a change came from someone outside the Fastrepl org.269- All H3 sections match the `SP,IMG,SP` pattern.270- All images report `complete && naturalWidth > 0`.271- No stray artifacts: `/image`, orphan `/`, double spaces, duplicated greeting.272- Sign-off block intact.273 274Manual judgment:275 276- Sender, From, and Reply fields correct. Flag any reply-to change, since the body promises a277 personal reply and any call for testers routes to the same address.278- Preview text length. Clients truncate near 90 to 140 characters, so front-load the important content.279- The Download button URL lives in editor state, not the DOM. Click the button block and read the280 Link textbox in the right sidebar.281- State explicitly that only the Compose step was audited unless Audience, Schedule, and Goals were282 also checked.283 284## Reporting285 286Restate what changed in plain language, quote the new copy, and attach a screenshot of edited287sections. Disclose any self-inflicted document damage and its repair rather than hiding it.288 Discovery context
Discovered by repository scan. No exact path reference found in the snapshot’s root AGENTS.md.
