WRITING
Three Open-Source Contributions, In Retrospect: Raycast · WeChatTweak · Cardinal

Three Open-Source Contributions, In Retrospect: Raycast · WeChatTweak · Cardinal
In the first half of 2026 I sent one PR each to three upstream projects of completely different character:
- Raycast DocKit — a document-formatting extension published to the Raycast Store (TypeScript + Python backend)
- WeChatTweak WeChat 4.x anti-revoke — a macOS binary byte patch (Swift + ARM64 reverse engineering)
- Cardinal persistent search history — a feature added to a 1610⭐ Rust+Tauri file-search app (React/TS)
The three span three different worlds — extension ecosystems, systems reverse engineering, desktop apps — with different languages, methods, and hard parts. This isn't a brag list; it's a retro: for each, the necessity (why it had to be done), the method (how exactly), and where the bar for merging actually sat.
Overview
| Raycast DocKit | WeChatTweak 4.x anti-revoke | Cardinal search history | |
|---|---|---|---|
| Upstream | raycast/extensions | sunnyyoung/WeChatTweak | cardisoft/cardinal (1610⭐) |
| PR | #26665 · ✅ merged | #1039 · 🟡 open | #222 · 🟡 open · CI all green |
| Language/stack | TypeScript + Python CLI | Swift + ARM64 assembly RE | React + TypeScript |
| Diff size | +3499/-0 (11 files, mostly lock) | +114/-10 (5 files) | +517/-3 (22 files) |
| Source of necessity | My own heavy docx-formatting chores for institute reports | I wanted anti-revoke on WeChat 4.x | I often search several keywords at once |
| Hard part | PR-process hygiene + outlasting the stale-bot | Pinpointing the patch site in an unsymbolicated dylib | Framing it as a "supplement" not a duplicate, keeping a clean branch |
| Artifact | Live on the Store | fork + reproducible byte patch | self-compiled build already installed + PR |
The three "bars for merging" in one line: Raycast is about process hygiene, WeChatTweak about reverse-engineering rigor, Cardinal about upstream etiquette. In none of them was the code itself the hardest part.
1. Raycast DocKit: the code was fine — twice it died on "a badly written PR"
PR #26665 · merged 2026-04-30 · Live on the Store
Necessity
Writing reports at the institute, my Word/PPT/Excel files are full of repetitive Chinese-typesetting grunt work: unifying mixed Chinese/English punctuation, pairing up quotation marks, standardizing unit symbols, unifying PPT fonts and table styles, converting tables between xlsx/csv/txt. I'd long since written a Python CLI for this (python3 -m dockit), but every time I had to open a terminal and type the command. DocKit wraps that CLI into three no-view Raycast commands that run against the files selected in Finder with a single hotkey:
- Format Word — pair quotes, convert English punctuation to Chinese, standardize unit symbols
- Convert Spreadsheet — xlsx ↔ csv ↔ txt (target format from a dropdown)
- Standardize PowerPoint — unify fonts, fix text formatting, set table styles
Select files → trigger the command → HUD shows the result → output written back to the same directory.
Method, and the real retro point
Technically it isn't complicated: three no-view commands in TS, with run-dockit.ts calling the local Python backend. What's actually worth recording is that this was already my third time submitting an extension to raycast/extensions — the first two both stalled and got closed.

The lesson is blunt: the first two (ts2, quick-open-app) may well have had fine code; the cause of death was submitting the PR on a bare template — description blank, no demo recorded, no replies to review, and then worn down to auto-close by the stale-bot. DocKit did the opposite, the whole package: filled out the PR body (command table + How it works + install steps + live demo + an explicit screencast description), passed ray lint/tsc/CI, and — crucially — followed up actively: replied to every review comment, and even dropped a keep-alive comment right before the stale-bot could close it, until a maintainer approved.
For Raycast, getting merged relies on PR hygiene plus the patience to outlast the stale-bot — as much as on the code itself. Of the three contributions, this was the most "non-technical" lesson.
2. WeChatTweak WeChat 4.x anti-revoke: flipping one branch instruction inside an unsymbolicated dylib
PR #1039 · open · fork zengtianli/WeChatTweak
Necessity
Upstream WeChatTweak only covered up to WeChat 3.8.x — the revoke logic lived in the main binary Contents/MacOS/WeChat. WeChat 4.x moved that logic into Contents/Resources/wechat.dylib (a stripped C++ core). The result: the tool still patched the main binary at hardcoded paths, hit no revoke code at all on 4.x, and reported unsupported for every 4.x build number. To get anti-revoke on 4.x, you have to go into the dylib and find the patch site yourself.
Method: flip 4 bytes — and verify the bytes before flipping
The core is a single sentence — turn the conditional branch cbz w0, SKIP at the parseRevokeXML entry into an unconditional branch b SKIP. The revoke instruction still gets received and parsed, but the code that deletes the message can never be reached:

The rigor shows up in a few places — this is the dividing line between a reverse-engineering patch tool and "just tweak something":
- In-place, same-length replacement:
cbzandbare both 4-byte fixed length with the same target offset, so it only flipsE00F0034→7F000014and doesn't change the binary layout. - Address located by geometric signature, not by symbol (symbols are stripped): the
stpprologue at theparseRevokeXMLentry + thecbz w0atentry+0x270+ thestr x0atentry+0xA04, combined, are a unique hit across the whole arm64 slice — then verified byte by byte by disassembling. - Verify the original bytes before writing (the
expectedfield): only write if the target really isE00F0034; on the wrong WeChat version it reportsexpectedMismatchand refuses to write, so it won't blindly write and break WeChat. - Re-signing order: ad-hoc sign the modified dylib alone first, then
--deepsign the whole App, to avoid aCode Signature Invalidwhen execution reaches the modified code page. - Verification can only be done on a real device: get someone to actually send a message and then revoke it — it only counts if the revoke is really blocked. With symbols stripped, static analysis can't confirm it.
Later I also implemented a "keep the tip" variant (
--variant keeptip): instead of blocking parsing, it zeroes outnewmsgid(str x0→str xzr) so the deletion misses while the tip is still inserted. Along the way I corrected a directional mistake — early on I thought I had to NOP the downstream deletion call (which is on the virtual-dispatch receive side and hard to locate statically, so it was shelved), but later realized there was no need to find the deletion call at all: zeroing it out at the source is enough. Write down the wrong turns too — that's the value of a retro.
Bonus output: helping the community make sense of the "three walls"
In issue #1038 a bunch of "my version is different from yours" users were stuck, but they were actually stuck on three different problems — all muddled together under the same Unsupported version line. I drew it as one chain:

| Wall 1 · version wall | Wall 2 · path wall | Wall 3 · write wall | |
|---|---|---|---|
| What it reports | Unsupported version | Unsupported version (same error, different root cause) | You don't have permission to save "wechat.dylib" |
| Root cause | Build number's addresses all changed, not in config | The patch reads remote master by default and can't see the version you added locally | macOS 14+ App Management/TCC blocks modifying a signed app; sudo doesn't help (TCC checks the initiating process, not root) |
| Solved? | ✅ locate_revoke.py locates it automatically | ✅ changed the default to read config local-first | ⚠️ root cause identified; the fix = grant the terminal "App Management" permission — not a code problem |
Walls 1 and 2 are two different causes of the same error line, the easiest to conflate; wall 3 is another layer (write permission), unrelated to version or config path. Splitting them apart and drawing them on one chain is far more useful than replying "you probably mis-copied the addr" one by one — the most valuable contribution to an open-source project is sometimes not code, but diagnosing a messy problem cleanly.
3. Cardinal search history: how to make a change "worthy" of being merged
PR #222 · open · CI all green · MERGEABLE
Necessity
Cardinal is an "Everything-style" instant filename-search app for macOS (Rust+Tauri, self-built index; on my own machine it indexed 6.43 million files). I often need to look up several things at once, but its search history lived only in memory — gone when you close the app, and only reachable via the ↑/↓ keys, with no clickable UI. What I wanted was persistence plus a clickable dropdown panel.
Method: the feature itself + making it "mergeable"
The feature isn't technically demanding: a useRecentSearches hook that writes queries into localStorage (dedupe-to-top, cap of 50), and a clock button whose dropdown re-runs a query on click. The interesting design decision is when to record — not only on Enter, but also when you open a result (a search that led to "opening a file" is the search worth keeping):

But for a PR to a 1610⭐ repo, the real work is in making the change worthy of being merged:
- Check for prior work first, then frame it as a "supplement": upstream PR #31 (merged) had added in-memory ↑/↓ history navigation. Mine is persistence + a clickable panel — a supplement, not a replacement — and the PR text spells out this relationship and thanks #31 (using a separate hook file, not touching #31's code at all).
- Self-review and fix nits before opening the PR: an adversarial round of code review caught the dropdown misusing the
role="listbox"/"option"ARIA anti-pattern — in a repo that cares about accessibility, the maintainer would very likely ask for a change. So before submitting I changed it to plain button semantics +aria-haspopup, leaving no obvious handle. - Keep the PR branch clean: only the 22 files of the feature itself, excluding the CLAUDE.md/.claude I keep for my own fork — upstream shouldn't see those.
- Pass every CI gate locally before pushing:
format:check/typecheck/vitest(278 all pass) /buildall green before push. Result: all 10 upstream CI checks green on the first try.
Whether upstream merges you depends on whether you've thought through everything the maintainer would have to — checking prior work, thanking those who came before, fixing foreseeable review comments, keeping the branch clean, CI green. Etiquette is about minimizing the cost of reviewing you.
Cross-project retro: three bars for "upstream merge"
Having gone through all three, my biggest takeaway isn't a piece of code — it's seeing clearly where "getting merged" actually sticks in different ecosystems:
| Ecosystem | The real bar | My response |
|---|---|---|
| Raycast (extension Store) | Process hygiene + patience — the code might be fine, but an empty PR body + no follow-up = worn to death by the stale-bot | Fill out the PR, record a demo, reply to every review comment, bump past the stale-bot |
| WeChatTweak (systems RE) | Rigor — one wrong byte in an unsymbolicated binary breaks the app | Uniquely locate by geometric signature + verify bytes before writing + verify on a real device + honestly flag the unsolved parts |
| Cardinal (mature app) | Upstream etiquette — why should a maintainer spend time reviewing you | Check prior work and frame as a supplement, thank predecessors, fix nits before submitting, clean branch, CI all green |
A few general points:
- Every "necessity" came from my own real workflow — report typesetting, wanting anti-revoke, frequently searching multiple terms. I didn't hunt for projects to farm contributions; I had a pain point first and turned it into a public output along the way. That's what makes it sustainable.
- Honestly flagging "the parts I didn't solve" is worth more than pretending to be omnipotent — in WeChatTweak I wrote plainly that group chats have no tip and that the write wall is a permission problem, not a code one; in Cardinal I said plainly that
stringson the app binary can't catch the frontend, and that a11y verification relies on "source → build → CI" rather than grep. A retro has to hold up when I re-read it myself. - "Making others willing to merge" is a craft independent of writing code — it holds across all three ecosystems, and it's exactly what the first two failed Raycast PRs taught me.
What this means for my career
All three are publicly verifiable outputs at the intersection of domain + code (landing squarely on my core positioning of "hydro domain + code + AI applications") — far harder than a résumé line saying "familiar with XX," because the PR links, the Store listing, and the merge record can all be dropped on the table directly. The reverse-engineering one especially demonstrates "can chew on hard problems." The forward-looking anchor: once Cardinal #222 is merged upstream, it's a merged contribution to a 1610⭐ MIT project — worth staying on top of until it lands.
Source material: the three PRs themselves + ~/Apps/WeChatTweak/docs/{anti-revoke-patch,user-blockers}.md (reverse-engineering notes) + the self-compiled Cardinal fork (~/Dev/cardinal).