# Dead Code & Duplication Audit ## Summary The codebase is clean of obvious dead code. There are no `TODO`/`FIXME`/`HACK`/`XXX` markers in any `.ts`/`.tsx` file under `src/` (outside the skipped `src/generated/` and `src/e2e/` trees), no `@deprecated` tags, no commented-out code blocks, and no `if (false)` / unreachable branches. Knip's output is short and all its findings verify as real, though two are "tested-but-not-used" orphans rather than truly dead symbols. The genuine signal is concentrated in two places: 1. A small handful of exports that are only referenced from their own unit tests (archive barrel re-export, `useAccountCommunitiesWithMetadata`, `isArchiveView`/`isSettingsView`/`isNotFoundView`, `isDirectMediaUrl`, `getPreferredOrder`/`getRandomOrder`, `getCatalogPostHeightEstimate`/`getCatalogRowHeightEstimate`). Safe to either delete, make non-exported, or wire up a real consumer. 2. Duplicated logic, not duplicated files. The big offenders are `post-desktop.tsx` / `post-mobile.tsx` (they share `PendingModerationActions`, a moderation-options block repeated again in `mod-queue.tsx`, a manual `isAccountMod` computation that already has a hook, and the `.eth`/`.sol` display-name switch), plus the `useEditCommentPrivileges` hook carrying an unused `postCid` parameter. Outside those, there is one stale duplicate in `package.json` (`cross-env` listed in both `dependencies` and `devDependencies`) and one stale type shim (`declare module 'react-draggable'` for a package that is no longer installed). Total impact is measured in a handful of files — there is no bit-rot backlog here, just a cleanup pass. ## knip Findings `yarn knip` and `yarn knip:full` produced identical, short output (13 reportable lines, none `--no-exit-code` would have suppressed): | Knip category | Finding | Verification | |---|---|---| | Unused files | `src/views/archive/index.ts` | **confirmed**. The file is a barrel `export { default } from './archive';`. `src/app.tsx:39` imports `./views/archive/archive` directly, unlike every other view under `src/views/` which all go through their `index.ts`. | | Unused devDependencies | `cross-env` in `package.json:148` | **confirmed**. `cross-env` is legitimately used in `build`, `build-vercel`, `analyze-bundle`, and both `electron:start*` scripts, but it is declared **twice** — once at `package.json:25` (dependencies) and once at `package.json:148` (devDependencies). The devDependencies copy is redundant. | | Unused exports | `getCommunityIdentifier` in `src/hooks/use-community-identifiers.ts:7` | **confirmed**. Only consumed by the sibling `getCommunityIdentifiers` and `useCommunityIdentifier` in the same file. Not re-exported externally. | | Unused exports | `getCommunityIdentifiers` in `src/hooks/use-community-identifiers.ts:39` | **confirmed**. Only consumed by the sibling `useCommunityIdentifiers` in the same file. A same-named **local** `getCommunityIdentifiers` exists in `src/lib/utils/post-page-resolution.ts:67` (different signature) — knip is not confused, the module-level export really is internal-only. | | Unused exports | `REPLY_HEIGHT_DATA_ATTRIBUTE` in `src/lib/utils/pretext-height-estimates.ts:71` | **confirmed**. Used once inside the same file at line 853 as a DOM selector. No external consumer. | | Unused exports | `getCatalogPostHeightEstimate` in `src/lib/utils/pretext-height-estimates.ts:746` | **confirmed**. Only called internally at line 789 from `getCatalogRowHeightEstimate`. | | Unused exports | `getCatalogRowHeightEstimate` in `src/lib/utils/pretext-height-estimates.ts:780` | **confirmed**. Only called internally at line 804 from `getCatalogRowHeightEstimates`. | | Unused exported types | `ArchiveRouteRenderOptions` in `src/views/archive/__tests__/helpers.ts:10` | **confirmed**. The helper `renderArchiveRoute` is called with object-literal args at 5 sites in `archive.test.tsx`; none of the test files name the type. The `export` keyword on the type alias is surplus. | | Configuration hints | `android/app/src/main/assets/public/**` still in `ignoreFiles` | **likely-false-positive** (harness-specific). This path is listed as a deliberate skip for Android-bundled build output. Knip is offering to remove it because the directory doesn't currently exist in the checkout; leave it pinned unless the Android workflow has been retired. | | Configuration hints | `babel-plugin-react-compiler` still in `ignoreDependencies` | **likely-false-positive** (build tooling). It is loaded by Vite config as a string id and knip cannot trace it; the ignore entry is intentional. | Two categories knip did **not** surface but that were checked by hand and are real: test-only orphans (`useAccountCommunitiesWithMetadata`, `isDirectMediaUrl`, etc.) and type-stub drift (`declare module 'react-draggable'`). Knip skips the first because tests import them, and the second because it only inspects JS/TS imports, not ambient `.d.ts` declarations for absent packages. ## Findings ### Unused Exports - **src/views/archive/index.ts:1** — One-line barrel `export { default } from './archive'` that nobody imports; `src/app.tsx` uses `./views/archive/archive` directly. *Action:* either delete `index.ts` and leave the direct import, or flip `app.tsx` to `import Archive from './views/archive'` (the convention used by every other view). The second is the smaller-surface fix. `confirmed`. - **src/hooks/use-community-identifiers.ts:7,39** — `getCommunityIdentifier` and `getCommunityIdentifiers` are exported but only used by the React-facing `useCommunityIdentifier` / `useCommunityIdentifiers` in the same file. *Action:* drop the `export` keyword on both; keep them as module-private helpers. `confirmed`. - **src/lib/utils/pretext-height-estimates.ts:71** — `REPLY_HEIGHT_DATA_ATTRIBUTE` is only used inside the same file (line 853). *Action:* drop the `export`. `confirmed`. - **src/lib/utils/pretext-height-estimates.ts:746,780** — `getCatalogPostHeightEstimate` and `getCatalogRowHeightEstimate` are exported but only used within the file by `getCatalogRowHeightEstimate` / `getCatalogRowHeightEstimates` respectively. *Action:* drop the `export` on both. Only `getCatalogRowHeightEstimates` (line 799) needs to remain public. `confirmed`. - **src/views/archive/__tests__/helpers.ts:10** — `export type ArchiveRouteRenderOptions` is never referenced by name; callers pass object literals. *Action:* drop the `export` keyword. `confirmed`. - **src/hooks/use-account-communities-with-metadata.ts:5** — `useAccountCommunitiesWithMetadata` has zero production consumers; only `src/hooks/__tests__/selector-hooks.test.tsx:6` imports it. *Action:* delete the hook and its test cases, or wire it into the one plausible consumer (`BoardsBar`'s "my communities" section currently uses `useDirectories` instead). Knip didn't flag this because the test keeps the import alive. `confirmed`. - **src/lib/media-hosting/direct-url.ts:5** — `isDirectMediaUrl` and the `DIRECT_MEDIA_EXTENSIONS` list have no non-test consumer. The extension set is also a subset of `KNOWN_IMAGE_EXTENSIONS`/`KNOWN_VIDEO_EXTENSIONS` in `src/lib/utils/media-utils.ts:77-78`, so the logic duplicates the classifier used by `getLinkMediaInfo`. *Action:* delete `src/lib/media-hosting/direct-url.ts` and its test, or rewrite it as a thin re-export of a shared `classifyUrl()` helper backed by the `media-utils` extension lists. `confirmed`. - **src/lib/media-hosting/provider-order.ts:5,10** — `getPreferredOrder` and `getRandomOrder` are exported only to be exercised by `__tests__/provider-order.test.ts`. `getPreferredOrder` has no internal consumer either; `getRandomOrder` is called once at line 34 by the public `getProviderOrder`. *Action:* drop both `export` keywords and rewrite the tests against `getProviderOrder` with runtime+mode combinations that exercise the preferred and random branches. `confirmed`. - **src/lib/utils/view-utils.ts:85,93,72** — `isArchiveView`, `isNotFoundView`, and `isSettingsView` are only used internally (`isNotFoundView` composes the other two) and by `__tests__/view-utils.test.ts`. No component/route imports them. *Action:* if `NotFoundView` routing is still useful, wire `isNotFoundView` into `src/app.tsx`'s route-resolution logic (it currently duplicates the negation inline). Otherwise drop the `export` on all three and delete the tests. `confirmed`. - **src/hooks/use-author-privileges.ts:11** — The destructured return includes `accountAuthorRole` but no external consumer reads it (the two callers in `edit-menu` and `post-menu-mobile` destructure only `isAccountMod`/`isAccountCommentAuthor`/`isCommentAuthorMod`/`commentAuthorRole`). *Action:* drop `accountAuthorRole` from the returned object. `confirmed`. - **src/hooks/use-author-privileges.ts:5-9** — The `AuthorPrivilegesProps` interface declares `postCid?: string` but the hook body never reads it; `edit-menu/edit-menu.tsx:52` passes a `postCid` in anyway. *Action:* delete the `postCid` field from the interface and the `postCid,` from the destructuring at the edit-menu call site. Dead parameter. `confirmed`. ### Dead Code - **src/modules.d.ts:8** — `declare module 'react-draggable'` type stub for a package that is not installed (`node_modules/react-draggable` does not exist, no `react-draggable` entry in `package.json`, zero `from 'react-draggable'` imports under `src/`). *Action:* delete the line. `confirmed`. - **src/modules.d.ts:6** — `declare module 'lodash'` is a permissive any-typed stub, but `@types/lodash` **is** installed (`package.json` declares it at the top level and `node_modules/@types/lodash/package.json` exists). The stub silently broadens types the installed package would otherwise provide. *Action:* delete the stub. If tsgo complains about a specific submodule import (e.g. `lodash/capitalize`), the correct fix is to import from `lodash` or add a typed helper, not to re-shadow with `any`. `confirmed`. - **package.json:148** — `cross-env` duplicated in `devDependencies` (it is already a direct `dependencies` entry at line 25 at the same version). *Action:* remove the devDependencies line so the package surfaces in exactly one group. `confirmed`. Nothing else in this category turned up: no `TODO`/`FIXME`/`HACK`/`XXX` markers, no commented-out logic, no `@deprecated` tags, no `if (false)` / unreachable `default:` branches, no empty `.module.css` files. ### Duplication Candidates - **src/components/post-desktop/post-desktop.tsx:99-209 vs src/components/post-mobile/post-mobile.tsx:115-210 vs src/views/mod-queue/mod-queue.tsx:209-281** — The `usePublishCommentModeration(approve)` + `usePublishCommentModeration(reject)` pair is set up **three times** with the same `onChallenge`/`onChallengeVerification`/`onError` trio and the same `handleApprove`/`handleReject` double-confirm wrapper. Post-desktop additionally wraps it in a `PendingModerationActions` subcomponent, which post-mobile redoes inline inside `PostInfoAndMedia`. *Shared abstraction:* `useApproveRejectModeration({ cid, communityAddress, post })` in `src/hooks/`, returning `{ approve, reject, approveState, approveError, rejectState, rejectError, handleApprove, handleReject }`. Callers render the UI. The hook owns the two `usePublishCommentModeration` calls, the shared onChallenge/onError, and the `window.confirm(t('double_confirm'))` guard. This would delete roughly 90 lines across the three call sites. - **src/components/post-desktop/post-desktop.tsx:253-254 + 109-112 vs src/components/post-mobile/post-mobile.tsx:108-109 + 124-127** — Both files manually compute `const accountRole = roles?.[accountAddress]?.role; const isAccountMod = accountRole === 'admin' || accountRole === 'owner' || accountRole === 'moderator';`. The exact same `isAccountMod` logic already lives in `src/hooks/use-author-privileges.ts` (which is already consumed by `edit-menu` and `post-menu-mobile`). *Shared abstraction:* call `useAuthorPrivileges({ commentAuthorAddress: address, communityAddress })` in both post components instead, destructuring `isAccountMod`. Delete the manual ternary. Bonus: the post components would also pick up `useCommunityField`-level selector memoization instead of reading `roles` off a prop. - **src/components/boards-bar/boards-bar.tsx:195, src/components/board-header/board-header.tsx:98, src/components/post-desktop/post-desktop.tsx:642,857, src/components/post-mobile/post-mobile.tsx:85, src/views/mod-queue/mod-queue.tsx:639** — Six sites inline the check `address.endsWith('.eth') || address.endsWith('.sol') ? address : getShortAddress(address)` to decide whether a community address should be displayed verbatim or truncated. Two sites (`post-desktop`, `post-mobile`) do it twice. *Shared abstraction:* `formatCommunityAddressForDisplay(address)` in `src/lib/utils/string-utils.ts` (or a tiny `is-domain-like-address` predicate used by the existing `getShortAddress` fallback). When the TLD list grows (the community shortlist already includes `.sol`; IPNS keys are a separate branch), you update it once. - **src/components/post-desktop/post-desktop.tsx:72-78 vs src/components/post-mobile/post-mobile.tsx:62-67** — Identical `RepliesFooter` component (`hasMore ?