M3: frontend/Dockerfile now copies package-lock.json and uses npm ci instead
of npm install, for reproducible builds.
M4: replace em dash with plain hyphen in manual_share.py's attachment-only
fallback text (standing style preference; ping.py title and
TriageList.vue's sender fallback were already fixed in prior commits).
M5: Caddyfile.snippet uses basic_auth instead of the deprecated basicauth
directive alias (Caddy 2.8+).
M1/M2/M6 (unused discord import removal, None-payload guard in
_extract_body, discord.NotFound guard in thread_ingest.py) were already
included in the preceding two commits since they touched the same files.
Separate display snippet from raw_content: use actual message.content
for raw_content (which may be empty string) and only use the placeholder
string for the display snippet. This matches the precedent from Task 5's
ping.py where build_ping_embed must never substitute raw_content with a
placeholder, preserving the factual state of whether content was present.