refactor: delete the dead messaging island + add knip to CI (#92) - #95
Open
TortoiseWolfe wants to merge 4 commits into
Open
refactor: delete the dead messaging island + add knip to CI (#92)#95TortoiseWolfe wants to merge 4 commits into
TortoiseWolfe wants to merge 4 commits into
Conversation
…r wired) useConversationRealtime, useConversationRealtimeSync, decrypt-message and decryption-cache formed a closed island: they imported each other and nothing outside imported any of them. Verified beyond static imports — no dynamic import() with a matching specifier, no string-built module paths. Born dead, not orphaned. useConversationRealtime was present in the initial commit (893f552) and page.tsx in that same commit already had the full-refetch loadMessages. `git log --all -S` over src/app/ and src/components/ returns empty across all history, with a control query confirming the search works. It had already cost real time twice. On 2026-03-24 a5d4e3d added 55 lines of polling fallback into the dead hook to fix CI flake; it fixed nothing, and 812cead hand-copied the logic into page.tsx twelve hours later ("was dead code"). More recently it produced a false root cause on #69, whose diagnosis named clearDecryptionCaches() — a function that cannot execute. No harvest was needed after all. The two candidates in #92 both turned out moot: upsertMessage's id-based dedupe cannot replace page.tsx's content heuristic while optimistic rows carry `optimistic-<ts>` ids and server rows carry UUIDs (blocked on #91), and the live path already has all three failure strings (message-service.ts:767, :859, plus EncryptionLockedError). Refs #92, #90, #69 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ibers) keyManagementService.onKeysChanged() existed to let the shared-secret cache in useConversationRealtime invalidate on key rotation. That cache is gone, and the API had zero production subscribers even before it went — its own docstring named the dead consumer. Removes the type, the listener Set, onKeysChanged, notifyKeyChange, and its five call sites, plus the one test that exercised it. Also drops two comments in key-service.test.ts asserting a listener "in decrypt-message.ts" that never existed in that file — one of the docstrings that made #69's false root cause look well-sourced. Kept separate from the island deletion so it can be reverted independently: this touches a live service, whereas the island deletion touched nothing that runs. Note a listener is the wrong mechanism for cache invalidation here anyway — static-static ECDH means K(A,B) changes when *either* side rotates, and nothing subscribes to peer key changes. Key-epoch memoization with evict-and-retry is the shape that self-heals; see #92. Refs #92 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four places documented or scheduled work against modules that never ran: - SECURITY-ARCHITECTURE.md described the shared-secret cache as a live DoS mitigation, with a "~50ms to ~1ms" figure and a pointer to useConversationRealtime.ts. The mitigation was never in effect. Replaced with what the shipping path actually does: one ECDH derivation per getMessageHistory() batch, amortized across up to 50 messages, with no client-side cache. Records what that gives up (nothing security-relevant; cost is tracked in #91) and what it gains (rotation and revocation self-heal, no long-lived key material in module scope). - real-time-delivery.spec.ts credited the dead sync hook with setting data-messages-subscribed; it is set by messages/page.tsx. - specs/010-group-chats T038 and plan.md scheduled MODIFY against the deleted hook — retargeted at messages/page.tsx. T040's typing-indicator half now names useTypingIndicator, which is the hook that actually runs. No references to the island remain anywhere in the repo. Refs #92 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TypeScript's unused-symbol checks do not span modules, so a closed island — a group of files importing each other with no entry point from the app — type-checks and builds cleanly forever. Nothing in this repo could detect that shape. One survived from the initial commit to 2026-08 (#92), and the same class produced the 131 files deleted in #82 and the leads in #90. knip.jsonc configures app-router entry points and disables knip's built-in Next plugin, whose assumptions produce false entries against this static export. The gate is baselined: 37 pre-existing findings are listed under `ignore` so CI is green on arrival and fails only on NEW unreferenced files. Two distinct categories are in there and the config says so — one-line barrels re-exporting live components (an import-style choice, explicitly out of scope per #82) and genuine orphans awaiting individual verification under #90. It is a burn-down list, not an allowlist. .storybook/mocks/** is ignored for a different reason: those files are reached through path.resolve aliases in .storybook/main.ts:43,47, which knip cannot follow. Reachable, not orphaned. Mutation-checked: planting a new unreferenced file under src/lib/ turns the gate red and names the file; removing it returns exit 0. Refs #92, #90, #82 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #92. Resolves the messaging cluster of #90.
Four modules (719 LOC) plus three test files formed a closed island — importing each other, with nothing outside importing any of them. Verified beyond static imports: no dynamic
import()with a matching specifier, no string-built paths.Born dead, not orphaned.
useConversationRealtimewas in the initial commit, andpage.tsxin that same commit already had the full-refetchloadMessages.git log --all -Soversrc/app/andsrc/components/returns empty across all history (control query confirms the search works). There is nothing to roll back to.It had already cost real time twice. On 2026-03-24,
a5d4e3dadded 55 lines of polling fallback into the dead hook to fix CI flake; it fixed nothing, and812ceadhand-copied the logic intopage.tsxtwelve hours later — its title says "was dead code". More recently it produced a false root cause on #69, which namedclearDecryptionCaches()as the live cause of message loss. That function cannot execute.Changes
c1c6217)onKeysChanged/notifyKeyChange+ five call sites + its one test (30abb17) — kept a separate commit because it touches a live service, so it can be reverted independentlyd0d1d3d) — includingSECURITY-ARCHITECTURE.md, which described the never-wired cache as a live DoS mitigationknipwith a baseline and a CI gate (325d114)No behaviour change
Both harvest items in the issue turned out moot, which makes this a pure deletion:
upsertMessage's id-based dedupe cannot replacepage.tsx's content heuristic while optimistic rows carryoptimistic-<ts>ids and server rows carry UUIDs. Blocked on Messaging live path: 5 round-trips per event, lost-update race, content-based dedupe #91.message-service.ts:767,:859, plusEncryptionLockedError) — the issue's claim that it had only one was wrong.knip
Baselined at 37 findings so the gate is green on arrival and fails only on new unreferenced files. The baseline holds two deliberately-distinct categories, labelled in
knip.jsonc: 16 one-line barrels re-exporting live components (an import-style choice, out of scope per #82), and 21 genuine orphans for #90 to burn down. It is a burn-down list, not an allowlist..storybook/mocks/**is ignored for a different reason — reached viapath.resolvealiases knip cannot follow.Mutation-checked: planting an unreferenced file under
src/lib/turns the gate red and names it; removing it returns exit 0.Verification
type-checklintThe coverage concern raised in the issue (deleting well-covered LOC might trip the 58% gate) was flagged there as unmeasured. Measured: it does not.
Note on ordering
pnpm buildcould not be verified locally on this branch — that is #93, fixed in #94, which should land first. CI verifies the build here.🤖 Generated with Claude Code