Commit Graph
48 Commits
Author SHA1 Message Date
Jamie BruntonandClaude Opus 5 4404ae75ff feat: kill conversations on off-platform and seller pushes
Seller detection on replies was a case-insensitive substring match over the
configured keyword list, and every hit landed in `human-required` alongside
genuine prospects that need a person. Off-platform pushes were only checked
for r4r policy modes, as were the under-18 signals.

Three layers now:

- New `off-platform-signals` module matches on normalized text (unicode
  folding, zero-width stripping, leet mapping, separator removal), so
  `0nlyf4ns`, `t3l3gr4m` and `t e l e g r a m` are caught. Ambiguous terms
  need context: `snap` on its own stays a photo request, never a kill.
- New terminal `killed` automation status. It records the evidence, sets the
  durable seller flag, supersedes the pending claim, and resolves triage to
  `done`, so killed conversations leave every actionable queue. Off-platform
  kills are independent of `reddit.filterSellers`.
- Optional AI screening of inbound replies behind
  `conversations.aiReplyScreening` (off by default). Runs only after the
  deterministic gates pass, memoizes per inbound event, and fails open.

Under-18 and in-person/contact-detail signals now apply to every policy mode
rather than r4r only, and remain human handoffs rather than kills.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:53:00 +01:00
Jamie BruntonandClaude Opus 5 ac12a3146a feat: recover missing conversations, fix media attachment, add manual inbox tab
Missing conversations: recipient Reddit accounts that are deleted or suspended
left conversations unaccounted for. A new RecipientAccountStatusService polls
Reddit for the users behind cached conversations and records their account
status durably, with progressive backfill and delta-media fixes in ChatService
so history and images are no longer dropped mid-sync.

Media: attachMedia now surfaces download failures as MediaAttachmentError so the
sync cursor can be retried instead of silently losing an image, recognizes image
bytes when Matrix returns a generic content type, and keeps separate gallery
occurrences when the same asset appears in more than one event.

Manual inbox tab: the conversations list now carries the reply-loop status, so
the inbox can separate conversations the operator is driving by hand from the
ones automation is still handling. A Manual filter pill and a per-row chip
explain why each conversation was handed over.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:03:33 +01:00
Jamie BruntonandClaude Opus 5 725b5fc4e6 feat: application-level read state and inbox triage for conversations
Conversations were hard to manage because "unread" came straight from
Matrix's notification_count, and nothing in the app ever sends a read
receipt — so opening a conversation in the dashboard could never clear it
and the unread set only ever grew. The list also collapsed three
orthogonal facts into one word, labelling "they spoke last and we have
not replied" as "Up to date".

Read state is now ours:

- conversation_read_state table (migration 19) plus
  ConversationReadStateRepository, kept separate from conversation_states
  so a read write can never be lost to mergeConversationState. The
  watermark is monotonic, so a stale poll cannot resurrect an unread
  badge.
- hasUnreadInbound and needsReply are derived server-side and returned on
  every conversation. Matrix's `unread` stays on the contract but the
  dashboard no longer reads it.
- POST /conversations/{id}/read|unread|status and
  POST /conversations/bulk-read, with optimistic cache updates.

Inbox rework:

- List rows are a real grid (checkbox, open button, chip strip) instead of
  a full-bleed absolute overlay button, which previously forced a
  pointer-events escape hatch for anything interactive in a row. Rows use
  aria-current rather than aria-pressed. Not a listbox: options must not
  contain interactive descendants.
- Counted filter pills replace the select, independent state chips replace
  the collapsed status word, plus a sort control (including oldest
  unanswered), a per-account filter, age-coloured timestamps, and an
  explicit count of rows hidden by the seller filter.
- Keyboard: j/k/arrows, Enter, u, e, / and Escape. Shortcuts yield to any
  open dialog so Escape dismisses the modal, not the conversation behind
  it. Multi-select with bulk mark-read and mark-done.
- Detail panel: auto-mark-read on open, a "New messages" divider pinned at
  mount (deriving it from the live watermark made it vanish a second after
  opening), triage actions in the header, the automation banner moved out
  of the composer, day dividers with time-only stamps, and a flex column
  so history scrolls in its own region instead of painting over the
  composer.
- Row order freezes while a conversation is open, with a "N new — refresh"
  pill, so a background poll cannot reorder rows mid-triage.

Two database tests rewound user_version by an offset from the latest
version, which silently re-targets a different migration whenever one is
appended. They now pin absolute versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 10:06:03 +01:00
Jamie BruntonandClaude Opus 4.8 c8689af04d feat: decouple manual image sharing from automation gating
The "Share a saved image" panel was wired to the automated exchange
engine, so an operator could only share an image right after an inbound
image request, limited to assets the auto-exchanger would itself pick.

Add a manual path that offers any enabled, unused library image at any
point (no request detection, tier, seller, or boundary gating) and sends
it with no reciprocal "your turn" text. Safety flags now surface as a
non-blocking warning instead of disabling the panel. The automated
auto-send path keeps all its guardrails; the shared image-delivery step
is extracted into a helper used by both paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-10 09:18:37 +01:00
Jamie BruntonandClaude Fable 5 e984bf20ac feat: dashboard UI/UX overhaul from full review
Shared foundation:
- New Modal component on native <dialog>.showModal() (focus trap, inert
  background, Escape, focus restore, scroll lock); all eight dialogs and
  both globalThis.confirm calls migrated to it
- Design tokens for success/warning/danger surfaces; pill danger variant;
  .sr-only utility; toast layering above dialog backdrops; not-allowed
  cursor for validity-disabled buttons

Conversations/attention: auto-scroll with jump-to-latest, per-conversation
draft persistence, suggestion undo, 30s auto-refresh with refresh controls,
unread emphasis and counts, blur-consistent share dialog, empty-accounts
guidance, collapsed user lookup, relative timestamps, danger pills for
critical items

Media/library/outreach: click-blurred-to-reveal, lightbox with arrow
navigation and captions, dirty-form preservation across refetches, upload
validation with thumbnail preview, save feedback, result counts with
load-more and clear-filters, ticking countdowns from a single clock

Overview/performance/operations/settings: mutation errors inside the
confirm dialog, live sending-window countdown, offline-aware loading
states, per-panel loading/error with retry, 7/30/90-day range toggle,
console scroll yielding with shown/retained counts, risky-save matching
by subreddit name instead of index, useBlocker unsaved-changes guard,
single-pass validation with focus-to-first-error, access panel JSON
validation and input reset

Tests: 806 -> 844 passing; both typechecks clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 09:07:42 +01:00
jjbrunton b573b88b08 feat: add conversation image previews and sharing 2026-08-09 22:28:41 +01:00
jjbrunton bf474dfe10 Add consent-gated media exchange library 2026-08-09 19:24:38 +01:00
jjbrunton e9083281a8 feat: configure ready outreach funnel limit 2026-08-09 16:13:58 +01:00
jjbrunton 22eb32e5bf Show per-account delivery status 2026-08-09 16:12:05 +01:00
jjbrunton f55ae7ddf8 Add active outreach funnel dashboard 2026-08-09 15:54:05 +01:00
jjbrunton 92c54feedc Filter detected sellers from conversations 2026-08-09 14:38:03 +01:00
jjbrunton b73e305cf3 Add hybrid R4R media conversation mode 2026-08-09 11:45:45 +01:00
jjbrunton 0f76bcf59a Add per-subreddit R4R conversation modes 2026-08-09 11:30:32 +01:00
jjbrunton 50f9f8a6aa Show Reddit post age in conversations 2026-08-09 09:54:53 +01:00
jjbrunton 83b5f1f5b6 Add attention inbox and AI usage observability 2026-08-09 08:52:32 +01:00
jjbrunton a85b99ceb6 Add notification centre and realtime toasts 2026-08-09 08:08:44 +01:00
jjbrunton b400042244 Fix activity account badge sizing 2026-08-09 08:07:42 +01:00
jjbrunton e7c29f91f2 Blur conversation images by default 2026-08-09 07:55:51 +01:00
jjbrunton acc610c2bf feat: link media to conversations 2026-08-09 07:32:26 +01:00
jjbrunton e8f56b42e6 feat: hand off upsell conversations to humans 2026-08-09 07:26:04 +01:00
jjbrunton 5ad7f1bf77 feat: label AI-generated conversation messages 2026-08-08 23:23:26 +01:00
jjbrunton 3beb8aada2 feat: automate bounded image-sharing conversations 2026-08-08 23:10:52 +01:00
jjbrunton dcf57273e2 Disable unavailable subreddits after Reddit 404 2026-08-08 22:53:24 +01:00
jjbrunton e8b730b592 feat: add agent API and operational analytics 2026-08-08 22:08:14 +01:00
jjbrunton 5a5a30a1d4 Improve messaging and dashboard reliability 2026-08-08 21:09:20 +01:00
jjbrunton 345ec99348 Improve Reddit chat and media reliability 2026-08-08 20:37:25 +01:00
jjbrunton 48492d5238 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	src/browser/client.ts
#	src/services/reddit-session-importer.ts
#	src/web/routes/conversations.ts
2026-08-08 20:17:19 +01:00
jjbrunton 34ae532cdd Fix chat visibility and conversation sync 2026-08-08 19:29:40 +01:00
jjbrunton 16a31335d0 feat: support concurrent Reddit accounts 2026-08-08 19:00:38 +01:00
jjbrunton d819a0f1e6 feat: track received image outcomes 2026-08-08 18:53:11 +01:00
jjbrunton 2c100d72b6 feat: persist settings and enrich conversation context 2026-08-08 18:18:28 +01:00
jjbrunton a36723a325 chore: remove retired native login paths 2026-08-08 16:55:04 +01:00
jjbrunton ea92a1a92b fix(auth): unify imported sessions with account gating 2026-08-08 16:53:55 +01:00
jjbrunton 654bbda457 feat(dashboard): add full-size media preview 2026-08-08 16:49:18 +01:00
jjbrunton c483c7b8be feat: replace browser login with imported Reddit sessions 2026-08-08 16:48:59 +01:00
jjbrunton 66a1146562 feat(dashboard): activate routed media filters 2026-08-08 15:43:53 +01:00
jjbrunton d108903174 Harden dashboard realtime state and controls 2026-08-08 15:41:58 +01:00
jjbrunton 4b3f00729f feat(dashboard): close legacy parity gaps 2026-08-08 15:41:34 +01:00
jjbrunton 70d62b2da3 feat(dashboard): route settings and access 2026-08-08 15:26:40 +01:00
jjbrunton 79de6aa2a1 feat: add settings and access dashboard slice 2026-08-08 15:25:34 +01:00
jjbrunton 89f7376b1b fix(dashboard): deduplicate realtime log replay 2026-08-08 15:15:54 +01:00
jjbrunton e003b0d8b2 feat(dashboard): route performance and operations 2026-08-08 15:11:48 +01:00
jjbrunton 5917b63755 feat(dashboard): add performance and operations slices 2026-08-08 15:10:52 +01:00
jjbrunton 20fa83f245 feat(dashboard): route conversations and media 2026-08-08 15:06:19 +01:00
jjbrunton 768470204b feat: add private media dashboard slice 2026-08-08 15:05:30 +01:00
jjbrunton 8d4eae7ab5 feat(dashboard): add conversations workspace 2026-08-08 15:05:08 +01:00
jjbrunton d6d29badcd feat(dashboard): align contracts and production build 2026-08-08 14:58:27 +01:00
jjbrunton 51e3e28a9b feat(dashboard): add React foundation and overview 2026-08-08 14:53:53 +01:00