110 lines
8.2 KiB
Markdown
110 lines
8.2 KiB
Markdown
# Shared modules audit (classic revenge-plugins)
|
|
|
|
Date: 2026-08-16
|
|
Scope: `/root/revenge-plugins` only (Next repo not covered). Audit only — no code changed.
|
|
|
|
Goal: (1) give a reference sheet of what `shared/` already offers so new/edited plugins reach for
|
|
it instead of reinventing, and (2) list concrete cases where 2+ plugins built the same thing
|
|
independently, so we know what's actually worth promoting to `shared/` next.
|
|
|
|
## 1. Reference: what's already in `shared/` and who uses it
|
|
|
|
| Module | What it does | Adoption |
|
|
|---|---|---|
|
|
| `ui/table.ts` | `TableRow`/`TableRowGroup`/`TableSwitchRow`/`TableRadioRow` — Discord's redesigned settings rows, falling back to legacy `Forms.Form*` | 24 files — well adopted, the default for any settings screen |
|
|
| `ui/SettingsScaffold.tsx` | ScrollView + bottom padding wrapper for a plugin's settings screen | 19 files — well adopted |
|
|
| `lib/color.ts` | `isValidHex`, `normalizeHex`, `resolveSemanticColor`/`resolveSemanticColorSafe` (theme-aware color token resolution) | 17 files — well adopted |
|
|
| `ui/NoteBox.tsx` | Bordered callout box for explanatory settings text | 15 files — well adopted |
|
|
| `ui/PrimaryButton.tsx` | Standard filled action button | 9 files — well adopted |
|
|
| `lib/rawFind.ts` | Walks `window.modules` directly for a module lookup that survives Revenge's negative-result caching | 8 files |
|
|
| `lib/patcher.ts` | `safePatch`/`applyPatches` — one broken patch surface can't crash a whole plugin's `onLoad` | 8 files — every plugin should use this for `onLoad`, worth spot-checking stragglers |
|
|
| `lib/createElementIntercept.ts` | Intercepts `React.createElement`/`jsx`/`jsxs` to replace or transform components that aren't exported and can't be `after()`-patched directly | 7 files |
|
|
| `lib/flux.ts` | `fluxSubscribe(event, cb, once?)` with built-in unsubscribe | 4 files |
|
|
| `lib/waitFor.ts` | Polls until a lazily-registered module/value appears, cancelable | 4 files |
|
|
| `ui/ListSection.tsx` | Titled section of tappable rows with an empty-state note | 3 files |
|
|
| `lib/lazy.ts` | Memoizing wrapper for a Metro lookup (resolve once, cache forever) | 2 files — low adoption, worth checking for plugins hand-rolling the same "cache after first resolve" pattern |
|
|
| `lib/patchRows.ts` | `patchRows(handler)` — hooks the native chat row bridge (`DCDChatManager.updateRows` / `RowManager.generate`) so a handler sees every message row as JSON | Used by rose-utils, role-color-everywhere, message-logger |
|
|
| `ui/ColorInput.tsx` | Hex color field with swatches | **1 file (radial-status)** — see finding #2, this is the stale version |
|
|
|
|
**Note on `patchRows.ts` vs the member-list "UserRow" pattern below:** these are unrelated despite
|
|
similar names. `patchRows` is for the *native chat message row bridge* (JSON crossing to native
|
|
code). The `findByTypeNameAll("UserRow")` pattern in finding #1 is a *React component* used in the
|
|
member list / profile popout. Don't conflate them when extracting shared code.
|
|
|
|
## 2. Findings: duplicated or fork-worthy code
|
|
|
|
### Finding 1 — `staff-tags` and `custom-user-tags` duplicate five files byte-for-byte
|
|
`ui/IconPicker.tsx`, `ui/Icon.tsx`, `lib/icons.ts`, and `patches/tag.tsx` are **100% identical**
|
|
between the two plugins (confirmed via `diff`, zero output). `ui/ColorInput.tsx` is also identical
|
|
between the two but has drifted from `shared/`'s version — see finding 2.
|
|
|
|
`patches/name.tsx`, `patches/details.tsx`, `patches/chat.ts`, `patches/profile.tsx` are not
|
|
byte-identical but are structurally the same patch applied twice: same target modules
|
|
(`DisplayName`, `UserRow`, `getTagProperties`, `UserProfilePrimaryInfo`), same comments explaining
|
|
*why* (e.g. "isn't a top-level export" copied verbatim into both `profile.tsx` files), and — not
|
|
in scope for this audit, but worth knowing — the **same latent bug** in both: the
|
|
`existingTag.props?.type !== 0` guard can't tell "a real Discord tag is here" from "the other
|
|
plugin already set a tag here," so having both plugins enabled causes one to silently clobber the
|
|
other's tag on a user. That's the parked staff-tags/custom-user-tags coexistence design (separate
|
|
task).
|
|
|
|
**Recommendation:** promote `IconPicker.tsx`, `Icon.tsx`, `icons.ts` to `shared/ui` and `shared/lib`
|
|
as-is (genuine, no-decision-needed duplication). Leave the four patch files alone for now — their
|
|
near-duplication is entangled with the coexistence bug fix, so refactor them together with that
|
|
work rather than as a mechanical move.
|
|
|
|
### Finding 2 — `shared/ui/ColorInput.tsx` is a stale stub; the real version lives unshared in two plugins
|
|
`shared/ui/ColorInput.tsx` is a minimal 2-swatch color field. `staff-tags/src/ui/ColorInput.tsx`
|
|
and `custom-user-tags/src/ui/ColorInput.tsx` (identical to each other) are a fuller implementation:
|
|
28 swatches, hex↔RGB conversion helpers, uses `normalizeHex` from `shared/lib/color`. Only
|
|
`radial-status` uses the shared stub today; everyone who needs a real color picker forked instead
|
|
of upstreaming.
|
|
|
|
**Recommendation:** replace `shared/ui/ColorInput.tsx`'s contents with the fuller version, point
|
|
`staff-tags`/`custom-user-tags`/`radial-status` at it, delete the two local copies. Check
|
|
`radial-status`'s usage still renders correctly with the richer swatch set before removing its
|
|
reliance on the stub.
|
|
|
|
### Finding 3 — `server-drawer` forked `createElementIntercept.ts` instead of extending the shared one
|
|
`server-drawer/src/patches/createElementIntercept.ts` is a deliberate fork of
|
|
`shared/lib/createElementIntercept.ts`, not accidental drift: it adds a `collapseAncestors` option
|
|
(ancestor-collapsing, used to hide the guilds bar and reclaim its layout space) that the shared
|
|
version doesn't have. 88 lines of diff, all additive on server-drawer's side.
|
|
|
|
**Recommendation:** this is a real capability gap in `shared/`, not a copy-paste duplication. Worth
|
|
upstreaming `collapseAncestors` into `shared/lib/createElementIntercept.ts` and switching
|
|
server-drawer back onto the shared module — but do it deliberately (it's shared infra used by 7
|
|
files across the repo; changing its behavior needs a look at every caller, not just server-drawer's
|
|
four files that reference it).
|
|
|
|
### Finding 4 — per-ID storage-map boilerplate repeated in at least 5 plugins
|
|
The pattern `storage.<key> ??= {}` followed by hand-written get/set/(sometimes remove) helpers for
|
|
a per-user or per-ID settings map shows up independently in:
|
|
- `staff-tags/src/lib/getTag.ts` (`tagSettings(id)`)
|
|
- `custom-user-tags/src/lib/tags.ts` (`allTags()`, `getUserTag()`, `setUserTag()`, `removeUserTag()`)
|
|
- `message-snippets/src/lib/snippets.ts`
|
|
- `radial-status/src/ui/Settings.tsx`
|
|
- `message-logger/src/index.ts` (single-key, simpler case)
|
|
|
|
**Recommendation:** a small `shared/lib/storageMap.ts` exporting something like
|
|
`createStorageMap<T>(storage, key)` → `{ get(id), set(id, value), remove(id), all() }` would
|
|
collapse ~15-20 lines per plugin into one call. Low risk, no behavior change, good first
|
|
extraction to pair with finding 1's icon files.
|
|
|
|
### Finding 5 — superseded, `raidens-themes` removed
|
|
Originally: `raidens-themes/src/patches/settings.ts` and `rose-plugs/src/patches/settings.ts`
|
|
independently solved "pin a settings entry into Discord's native App Settings" with different,
|
|
non-trivial APIs (272 lines of diff). `raidens-themes` was deleted from the repo on 2026-08-16, so
|
|
this is no longer a duplication to reconcile — `rose-plugs/src/patches/settings.ts` is now the only
|
|
implementation. Worth revisiting only if a future plugin wants the same capability; its
|
|
`SectionRow[]` API (`patches/settings.ts` exports `patchRosiesPlugsSection`) is the one to build on,
|
|
not extract into `shared/` preemptively with no second consumer.
|
|
|
|
## 3. Suggested order if/when this gets acted on
|
|
1. Finding 4 (storage map) — mechanical, zero behavior change, unblocks nothing else.
|
|
2. Finding 1's static files (icons/IconPicker) — mechanical, identical code, low risk.
|
|
3. Finding 2 (ColorInput) — mechanical but touches 3 plugins' rendering, needs a visual check.
|
|
4. Finding 3 (createElementIntercept collapseAncestors) — real shared-infra change, needs care.
|
|
5. Finding 1's patch files (name/details/chat/profile) — do together with the parked
|
|
staff-tags/custom-user-tags coexistence design, not standalone.
|
|
6. Finding 5 (settings pinning) — defer until a third consumer exists.
|