Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# thread-grouper
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Groups a flat list of emails into conversation threads using
In-Reply-To, References, and subject-line heuristics. Each thread is
returned with its full chronological message list plus a parent /
child tree rooted at the earliest message, so consumers can render
either a flat timeline or a nested conversation view.
## Features
- Parent resolution prefers `In-Reply-To` and falls back to the last
`References` entry.
- Subject-line normalisation strips `Re:`, `Fwd:`, `Aw:`, `Sv:`, `Vs:`
prefixes (with optional `[N]` ticket-tracker suffixes).
- A reply with a significantly different subject starts a new thread
even when it references the original — heuristic catches
"hijacked" reply chains.
- Stable chronological ordering via `Date.parse` on the email `date`
field (epoch-zero when missing, matching upstream behaviour).
- Records `email_processing.thread.grouped_total` with the produced
thread count per call.
## Usage: Basic
```ts
import { groupThreads } from "@suaveplan/email-processing/threading";
const threads = groupThreads(
messages.map((m) => ({
messageId: m.messageId ?? "",
inReplyTo: m.inReplyTo,
references: m.references,
subject: m.subject,
date: m.date,
}))
);
for (const t of threads) {
console.log(t.threadId, t.subject, t.messageCount);
}
```
## Usage: Advanced
```ts
import {
groupThreads,
normalizeSubject,
isSubjectChange,
parseReferences,
} from "@suaveplan/email-processing/threading";
const threads = groupThreads(messages);
function flatten(node) {
return [node.message, ...node.children.flatMap(flatten)];
}
const summary = threads.map((t) => ({
threadId: t.threadId,
subject: normalizeSubject(t.subject ?? ""),
flat: flatten(t.root).map((m) => m.messageId),
refs: parseReferences(t.messages.at(-1)?.references),
}));
```
## API Reference
- `ThreadableEmail` — `{ messageId, inReplyTo?, references?, subject?,
date?, from? }`.
- `ThreadNode` — `{ message, children }`.
- `EmailThread` — `{ threadId, subject?, root, messageCount, messages }`.
- `normalizeSubject(subject)`.
- `parseReferences(references?)` — `<id>` extraction.
- `isSubjectChange(original, reply)` — heuristic.
- `groupThreads(emails)` — returns chronological `EmailThread[]`.
## Implementation Notes
`buildTree` falls back to a `"messageId: ''"` placeholder root only
when the input array is unexpectedly empty — `groupThreads` itself
short-circuits empty input, so the fallback exists purely for type
safety. The grouper's chronological pass uses the most-recent
reference (last entry in `References`) when `In-Reply-To` is absent,
matching most MUA conventions. Subject-change detection is
case-insensitive and ignores whitespace differences.