Files
jakevin 644d45177b feat(twitter): add unlike + retweet + unretweet + quote (write-action symmetry P0) (#1400)
Round 21 P0 — Twitter write-action symmetry (4 of 4: unlike, retweet, unretweet, quote).

## Scope
Closes write-action gap with existing siblings (`like`, `bookmark`, `unbookmark`, `delete`):
- `unlike` (UI strategy, navigateBefore:true)
- `retweet` (UI strategy)
- `unretweet` (UI strategy)
- `quote` (UI strategy, `/compose/post?url=` route — same family as `reply.js` `/compose/post?in_reply_to=`)

+745/-0 in initial commit, plus 3 progressive review fixes. Final: 4 adapters + 4 tests; modified `shared.js`, `shared.test.js`, manifest, docs.

## Iteration history (4 heads, 102/102 tests on final)

- `07836783` — initial 4 adapters + 4 tests, 96/96
- `55a89776` — fix #1: shared `parseTweetUrl()` URL invariant + quote post-submit verify (102/102)
- `dc9eab66` — fix #2: article-scoping for unlike/retweet/unretweet (delete.js sibling pattern)
- `8809d2c1` — fix #3: exact status-id matching (`match?.[1] === tweetId`) + quote-card exact id guard

## 4 progressive blockers caught (codex-mini0 lead + F-P-0 aux)

1. **URL validation (silent-clamp class)**: original passed any host containing `/status/<id>`. Fixed: `parseTweetUrl()` requires `https` + Twitter/X exact host + exact `/<user|i>/status/<id>` path; host-suffix, embedded URL, path-suffix all `ArgumentError` pre-nav.

2. **Quote silent-success illusion**: original click-implies-success without composer/toast verify. Fixed: pre-submit quoted-card exact id render assertion + post-submit success toast OR composer-clear assertion, otherwise return failed row.

3. **Broad querySelector scoping (delete.js sibling pattern)**: original state probe + click + post-click verify on conversation pages picked first matching button. Fixed: scope to `article` containing requested exact status id (sibling `clis/twitter/delete.js:22-23` pattern).

4. **Substring vs exact status-id matching**: `/status/123` substring-matched `/status/1234`. Fixed: regex `/\/status\/${id}(?:\/|$)/` segment-edge anchor + `match?.[1] === tweetId` exact compare.

## Cultural sediment (Round 21)

**Audit checklist 5 rules (pre-write upstream selection net)**:
1. cross-grep sibling URL-construction patterns before adopting
2. silent-clamp class detection (any normalize-then-trust path)
3. broad querySelector → article-scoping requirement
4. missing-validation early reject before navigation/IO
5. ID-based DOM/URL matching exact-not-substring

**Augment framing**: Round 21 audit-first 是 Round 18 字面量 self-check 的 **upstream pre-write 阶段**, 两者作用阶段不同, 共存比替换稳。

**Meta-anchor "Structural exactness for identity matching"** unifying:
- URL layer (#1391 isFacebookAuthRedirectPath: `\.php` + `(/|$)` segment edge)
- URL parser layer (#1392 parseGrokSessionId: bare UUID exact / URL host-exact-or-subdomain + path-exact)
- DOM layer (#1400 article-scoping: status-id `/\/status\/${id}(?:\/|$)/` regex or pathname segment-array exact compare)

Common invariant: boundary-lock structural shape, 不 trust substring 模糊 — fuzzy match 是 silent failure 温床。

## Validation gates (final head `8809d2c1`)

Local: Twitter tests 102/102, `node --check` touched files, `npx tsc --noEmit`, `npm run build`, typed-error-lint 189/189, silent-column-drop 103/103, doc-coverage 140/140, docs:build clean, listing-id advisory unchanged 13, `git diff --check` clean, merge-tree clean.

GitHub: build×3 (ubuntu/macos/windows) SUCCESS, unit-test×2 shards SUCCESS, bun-test SUCCESS, adapter-test SUCCESS, audit SUCCESS, doc-coverage SUCCESS, docs-build SUCCESS, smoke-test skipped, PR `CLEAN/MERGEABLE`.

## Strategy/UI boundary (better-solution verdict)

UI write path acceptable for P0 symmetry (matches existing Twitter write siblings). GraphQL write migration + structured `idempotent:true` flag are cross-sibling upgrades, P5 candidate, not P0 blockers.

Round 17 race-mitigation 第 8 连续 race-free execution (this round absorbed author scope-uncertainty hold-then-retract event without producing actual race).

Reviewers:
- Lead: @codex-mini0 (4-round iteration, all blockers caught)
- Aux: @First-principles-0 (better-solution triangulation, scope-discipline verdict, regression invariants)
- Author: @opencli-user
2026-05-08 01:10:54 +08:00
..