Skip to content

release: ✨ targets system — multi-account content flows with two-stage evaluation#73

Merged
nj-io merged 215 commits intomainfrom
develop
Apr 3, 2026
Merged

release: ✨ targets system — multi-account content flows with two-stage evaluation#73
nj-io merged 215 commits intomainfrom
develop

Conversation

@nj-io
Copy link
Copy Markdown
Owner

@nj-io nj-io commented Apr 1, 2026

What's New

This release introduces the targets system — multi-account, multi-destination content flows with per-target content strategies. 215 commits, 339 files changed, +52K/-7K lines.

Targets: Multi-Account Content Flows

Configure multiple accounts across platforms, each with its own strategy, audience, and posting schedule. A single commit can produce different content for different audiences simultaneously.

  • Accounts — multiple accounts per platform (e.g., lead X account + product X account)
  • Targets — account + destination + strategy + scheduling. The pipeline unit.
  • Strategies — named, reusable content strategies (building-public, brand-primary, technical-deep-dive, product-news, custom). Each defines audience, voice, angle, post_when, avoid.
  • Target routing — evaluator decides per-strategy, routing layer maps strategies to targets, produces per-target drafts
  • Shared-group drafting — single LLM call produces per-platform variants when targets share a strategy

Two-Stage Evaluation Architecture

Replaces the single-stage evaluator with a commit analyzer (stage 1) + strategy evaluator (stage 2) pipeline.

  • Stage 1 (CommitAnalyzer) — classifies commits (trivial/routine/notable/significant), produces freeform tags, summary, brief update instructions
  • Stage 2 (Strategy Evaluator) — per-strategy decisions (skip/draft/hold) with topic matching, arc awareness, queue actions
  • Interval gatingcommit_analysis_interval setting batches commits. Deferred commits skip the expensive LLM pipeline until the threshold is crossed.
  • Freeform tags replace the fixed episode type taxonomy. Tags drive topic matching, display, and filtering.

Content Topic Queue

A persistent, prioritized queue of topics worth covering. The evaluator works through topics by priority rather than reactively posting about whatever commit landed.

  • Two topic types — implementation topics (from commit tags, code-driven strategies) and product topics (from brief, positioning strategies)
  • Topic granularity — low/medium/high setting controls auto-topic specificity via LLM-assisted extraction
  • Dismissed status — reject topics without them being auto-recreated. Excluded from evaluator context and tag matching.
  • Draft Now — force-draft on uncovered or held topics
  • Drag reorder, rank numbers, strategy filter, combine topics

OAuth 2.0 & Preview Mode

  • OAuth 2.0 — X adapter migrated from API keys. Generic web OAuth flow with PKCE for any platform. Token DB storage with automatic refresh.
  • Preview mode — targets without connected OAuth accounts produce preview drafts using real platform constraints. Connect an account later to go live.

Unified Logging & Diagnostics

  • LogBus — multi-sink logging pipeline (file, DB, SSE). Structured events visible in CLI (social-hook logs) and web UI (System > Event Log).
  • Pipeline diagnostics — structured health checks per evaluation cycle. Stored on cycles, surfaced in CLI and web UI.
  • System error feed — severity-based routing, 30-day TTL pruning, auto-refresh via WebSocket + polling.

Codebase Decomposition

  • Modelsmodels/__init__.py split into 7 submodules (enums, core, narrative, content, infra, context, _helpers). Direct imports only, no re-exports.
  • Triggertrigger.py decomposed into 6 sibling modules (trigger_git, trigger_context, trigger_decisions, trigger_side_effects, trigger_batch, trigger_secondary).
  • Shared utilitiesenum_value(), AdapterRegistry, OAuth PKCE, migration runner, scheduling algorithm all extracted for reuse.

Web UI

  • Evaluation cycles — inline approve/reject, Draft Now for holds, episode tags as pills, strategy outcome cards
  • Topic queue — rank numbers, dismiss, drag-drop per strategy group, created_by badges, useToast error handling
  • Settings — strategy CRUD (create/delete), target management, credential management, context config (interval, granularity)
  • Activity indicator — pulsing navbar dot during LLM work with per-stage progress labels
  • Error feed — auto-refresh via useDataEvents + polling fallback
  • Import history — limit control, branch selector, progress tracking

CLI

  • 9 new command groups: credentials, account, target, strategy, topics, brief, content, cycles, logs
  • topics dismiss, topics list --include-dismissed, topics draft-now on uncovered
  • decision batch-evaluate for manual batch evaluation
  • credentials add --set for non-interactive CI use
  • All commands follow conventions: --json, --yes, --project, help text with examples

Bot Integration

  • Bot LLM calls (evaluate, draft, promote, angle redraft, media spec) migrated from synchronous to background tasks with stage tracking
  • Frontend shows stage labels and error handling during bot operations

Test Plan

  • pytest: 3416 passed across Python 3.10, 3.11, 3.12
  • tsc --noEmit: clean
  • ruff lint + format: clean
  • mypy: clean
  • 19 targeted pipeline tests (interval gating, topic dismissal, strategy scoping, granularity, prompt assembly)
  • 180 targets feature tests (phases 1-6)
  • OAuth coverage tests (auth, terminal, factory, 401 retry)
  • Playwright verification (interval gating, topic queue, strategy CRUD, error feed)

Migration Notes

  • New DB tables: content_topics, content_suggestions, evaluation_cycles, draft_patterns, system_errors, oauth_tokens
  • New columns on drafts (target_id, evaluation_cycle_id, topic_id, arc_id, preview_mode) and posts (target_id, topic_tags, feature_tags, is_thread_head)
  • models/__init__.py is now empty — update imports to models.enums, models.core, models.content, etc.
  • trigger.py functions moved to submodules — backward-compatible re-exports in trigger.py

🤖 Generated with Claude Code

nj-io and others added 30 commits March 23, 2026 15:08
…ter registry

Drop OAuth 1.0a for X entirely. All X API calls now use OAuth 2.0 Bearer
tokens. This fixes media uploads (OAuth 1.0a media IDs rejected by v2)
and prepares the adapter layer for the targets feature.

- New oauth_tokens DB table for per-account user tokens
- New adapters/auth.py: shared token refresh with SQLite serialization
- XAdapter rewrite: Bearer auth, 401 retry with injected refresher
- AdapterRegistry: process-scoped adapter cache in scheduler
- Factory reads tokens from DB, env vars reduced from 4 to 2
- Setup wizard updated for OAuth 2.0 (client ID + secret, PKCE flow)
- Section U E2E overhaul: post-media, thread, quote scenarios,
  capability exercise registry, --pause on all live posts,
  platform discovery from config instead of hardcoded list
- Test PNG replaced with 100x100 (X rejects tiny images)
- Remove requests-oauthlib dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Matches the X Developer Portal callback URL configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The E2E harness creates an isolated temp environment with a fresh DB.
Tokens from oauth2_setup.py are in the user's real ~/.social-hook/social-hook.db.
Copy them into the harness DB when running --live so adapters can authenticate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…in Section U

The audience_introduced column was removed in a prior migration, replaced
by the platform_introduced table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config model: platform_credentials, accounts, targets, strategies
expansion in config.yaml. New config/targets.py with AccountConfig,
TargetConfig, PlatformCredentialConfig, PlatformSettingsConfig, and
validation. Backward compat auto-migration from legacy platforms config.
commit_analysis_interval and pending_drafts_cap added to ContextConfig.

DB schema: content_topics, content_suggestions, evaluation_cycles,
draft_patterns, system_errors tables. New columns on drafts (target_id,
evaluation_cycle_id, topic_id, suggestion_id, pattern_id), posts
(target_id, topic_tags, feature_tags, is_thread_head), projects
(brief_section_metadata).

Adapter registry: extended with get_for_account() for multi-account
keying. Factory gains create_adapter_from_account() and
resolve_platform_creds(). auth.py gains on_error callback.
LinkedInAdapter gains token_refresher + 401 retry.

System error feed: DB-backed ErrorFeed with severity routing, in-memory
cache fallback, module-level singleton. Wired into scheduler via
_ensure_error_feed().

Integration: target_id branching in _post_draft(), error feed wiring
in scheduler_tick(), target_id propagation to Post records.

Quality: _tick_single_draft uses record_post_success (no duplication),
redundant arc query eliminated, dead constants removed, consistent
sqlite3 timeout=5 across auth.py and error_feed.py.

2495 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ief)

Two-stage evaluation: StrategyDecisionInput replaces TargetDecisionInput.
Evaluator reasons per-strategy. ContextSourceSpec tells routing layer
what context to assemble for drafter.

Freeform tags: EpisodeTypeSchema enum removed. Content filter gate
removed. Strategy post_when/avoid replaces filter as gating mechanism.

Project brief: llm/brief.py with structured sections, incremental
updates, operator-edit preservation via per-section metadata.

ContentSource registry: 4 built-in resolvers (brief, commits, topic,
operator_suggestion). Evaluator specifies sources, routing assembles.

Target routing: routing.py with route_to_targets(). Primary first,
dependents last. Draft sharing via groups.

Pipeline rewiring: trigger.py iterates strategies when targets config
exists. Evaluation cycles, tag-to-topic matching, brief updates.
Full backward compat for legacy config.

Quality cleanup: dead skip_content_filter chain removed, dead
episode_type extraction removed, lint fixes applied.

2597 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Arcs rework: strategy-scoped with PROPOSED lifecycle. Added strategy
and reasoning fields. propose_arc(), activate_arc(), abandon_arc().
Max arcs per-strategy configurable via max_arcs_per_strategy.

Topic queue: topics.py with seed_topics_from_brief(), match_tags_to_
topics(), get_evaluable_topics(), force_draft_topic(). Discovery seeds
product-level topics after brief generation.

Operator suggestions: suggestions.py with create_suggestion(),
evaluate_suggestion(), dismiss_suggestion(). run_suggestion_trigger()
in trigger.py.

Quality: unused imports removed, dead variable cleaned, lint fixes.

2670 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brand-primary template: new strategy template for high-level marketing
content. get_template_defaults() for strategy reset support.

Brand-primary candidate operations: combine_candidates() merges 2+
held topics into single draft. trigger_hero_launch() assembles full
project context for hero-level content.

Cross-account scheduling: _check_cross_account_gap() with ~33% fixed
jitter. Integrated into both normal and post-now scheduler paths.

Notification grouping: notify_evaluation_cycle() groups per cycle.
Interactive buttons: Expand All, Approve All, per-draft View, arc
Approve/Dismiss. Backward compat with legacy notifications.

LinkedIn entity: personal profile vs organization page posting via
entity parameter. Org admin validation, factory pass-through.

2753 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CLI: 9 new command groups — credentials, account, target, strategy,
topics, brief, content, cycles, system. 85 tests.

Web API: ~30 new endpoints matching all CLI commands. LLM ops via
background tasks + 202. check_unknown_keys(strict=True). 74 tests.

Web UI: 11 new components — evaluation cycles, topic queue, brief
editor, content suggestions, error feed, settings pages. Uses
AsyncButton, useBackgroundTasks, useDataEvents.

2912 tests pass, 0 failures. tsc clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire evaluate_suggestion() and force_draft_topic() with full LLM
evaluation pipeline. Wire 3 web API endpoints (draft-now, combine,
hero-launch) to actual backend functions. Add __post_init__ validation
to ContentTopic/ContentSuggestion. Add ARC_STATUSES. Remove dead
EpisodeType enum. Add Approve All button to evaluation cycles UI.

2912 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix /api/projects/{id}/targets returning dict-of-dicts instead of
array. Auto-generate target name. Fix UI destination options.

2912 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## X Adapter Migration
- Drop OAuth 1.0a, all X API calls use OAuth 2.0 Bearer tokens
- New oauth_tokens DB table for per-account user tokens
- New adapters/auth.py: shared token refresh with SQLite serialization
- XAdapter: Bearer auth, 401 retry with injected refresher callback
- AdapterRegistry: process-scoped adapter cache in scheduler
- Factory reads tokens from DB, env vars reduced from 4 to 2
- Remove requests-oauthlib dependency

## Generic OAuth Web Flow
- OAuthPlatformConfig registry in setup/oauth.py (X + LinkedIn)
- Server endpoints: /api/oauth/{platform}/authorize, callback, status, disconnect
- Frontend: Connect/Disconnect/Re-authorize for any OAuth platform
- Reusable <Note> component (ui/note.tsx) with warning/info/success/error variants
- Callback URL derived from server (not frontend port)
- postMessage notification from callback popup to opener

## CLI & Setup
- Inline PKCE flow in setup wizard (no separate script needed)
- Setup wizard prompts for 2 fields (Client ID + Secret), not 4
- social_hook/terminal.py: shared getch(), copy_to_clipboard(), pause_with_url()
- TUI select uses physical line tracking (fixes wrapping bug)
- Auto npm install on social-hook web if node_modules missing

## E2E Testing
- Section U overhaul: post-media, thread, quote, reply exercises
- Capability exercise registry for future extensibility
- --pause on all live-posting scenarios with [c] copy URL
- Per-scenario selection: --only U-x-post-media,U-x-quote
- Platform discovery from config (not hardcoded list)
- Token copy from real DB for --live mode
- VCR record mode: always re-record on --live
- Test PNG replaced with 100x100 (X rejects tiny images)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_oauth.py: run_pkce_flow happy path + error cases, _exchange_code, _save_tokens
- test_terminal.py: copy_to_clipboard, getch, pause_with_url
- test_adapters_platform_factory.py: create_adapter for X (DB tokens), LinkedIn, errors
- test_adapters_x.py: validate/delete/upload 401 refresh retry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: OAuth 2.0 migration, generic web OAuth, E2E overhaul
First docs maintenance loop run. Enriched help text for under-documented
commands so both --help and the auto-generated CLI reference explain what
each command actually does, when to use it, and include examples.

Commands improved:
- quickstart, trigger, scheduler-tick, consolidation-tick
- discover, web, test
- draft approve/cancel/retry/schedule/quick-approve

Also adds site-docs/DOC_STATUS.md for tracking doc coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generic maintenance loop agent (.claude/agents/maintenance-loop.md)
that diffs a branch from last run, performs autonomous work, flags
new work for approval, and creates PRs. Works in both local and
remote environments (detects gh availability).

Includes docs-maintenance config (.claude/loops/docs-maintenance.yml)
as the first concrete use case.

Un-ignores .claude/agents/ and .claude/loops/ so remote agents
can read them from the repo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New helper: scripts/e2e/helpers/time_travel.py with
backdate_recent_records() and capture_timestamp() for simulating
time passage in E2E scenarios without waiting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New helper: scripts/e2e/helpers/decisions.py with
get_latest_evaluation(), format_evaluation_summary(), and
assert_strategy_actions() for structured evaluator output inspection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… drafting

Replace the synthetic "preview" platform with preview mode on real
platforms. Targets can now omit account (setting platform directly)
to draft with real constraints without a connected OAuth account.

Key changes:
- TargetConfig.account optional, new platform field + resolve helpers
- Strategies wired to evaluator at all 5 call sites
- preview_mode DB column replaces draft.platform=="preview" checks
- draft connect command links an account to enable posting
- Single drafter call per strategy group (shared_group mode)
- Wizard/quickstart use real X platform, no more preview option
- Legacy platform-based drafting path gets deprecation warning

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	scripts/e2e/sections/platform_posting.py
#	scripts/oauth2_setup.py
#	src/social_hook/adapters/auth.py
#	src/social_hook/adapters/platform/factory.py
#	src/social_hook/adapters/platform/registry.py
#	src/social_hook/config/yaml.py
#	src/social_hook/db/schema.py
#	src/social_hook/scheduler.py
#	src/social_hook/setup/wizard.py
#	tests/test_adapter_registry.py
#	tests/test_adapters_auth.py
#	tests/test_adapters_x.py
#	tests/test_db.py
#	tests/test_setup_wizard.py
#	web/src/components/ui/note.tsx
#	web/src/components/wizard/step-credentials.tsx
#	web/src/lib/api.ts
…e OAuth test

- Settings sections (credentials, accounts, strategies, targets,
  platform-settings) now convert API dict responses to arrays
- Fix React rendering 0 from integer && JSX pattern (!!coerce)
- Filter legacy "preview" platform from settings grid
- Add preview-mode / connected badge to platform cards
- Remove stale /api/accounts/oauth-callback test (replaced by generic OAuth)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix 12 mypy errors: TargetAction enum usage in routing, str|None
  annotations, exception variable scoping, Any return types
- Regenerate site-docs/cli/ for new draft connect command and
  targets command groups (account, brief, content, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- credentials.py: rename shadowed variable 'missing'
- registry.py: type _adapters dict as dict[str, PlatformAdapter]
- test_targets_db.py: use relative path for migrations dir (was
  hardcoded to worktree path, fails in CI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test files:
- test_cli_target.py (22) — list, add, disable, enable
- test_cli_account.py (27) — list, add, validate, remove
- test_cli_credentials.py (26) — list, add, validate, remove
- test_cli_content.py (22) — suggest, list, dismiss, combine, hero-launch
- test_api_connect.py (13) — draft connect + promote endpoints
- test_drafting_shared_group.py (16) — lead selection, unthread, shared group
- test_drafting_adapt.py (10) — cross-platform adaptation
- test_trigger_coverage.py (6) — strategy wiring, legacy path
- test_topics_coverage.py (15) — resolve_default_platform, force_draft
- test_suggestions_coverage.py (11) — create, dismiss, evaluate

Updated:
- test_draft_cli.py (+12) — draft connect command
- server.py — fix connect endpoint check_unknown_keys error handling

Total: 2963 → 3139 tests (+180)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
docs: enrich CLI docstrings and add DOC_STATUS tracking
- /api/projects/{id}/cycles now returns trigger (human-readable),
  strategies (per-strategy decision/reasoning/draft), and status
  (derived from draft statuses) — previously only raw DB fields
- Evaluate button uses AsyncButton with spinner + elapsed time counter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
nj-io and others added 28 commits March 31, 2026 07:32
…g decision

The batch evaluate endpoint passed all selected decisions as deferred AND
used the first one as trigger_commit_hash, causing duplicate hashes and a
UNIQUE constraint violation. Now the last decision is the trigger (with
existing_decision_id set so _run_targets_path reuses the row) and the
rest are deferred.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --limit / -n option to import-commits CLI and limit param to
web API (import-preview + import-commits). Returns the N most recent
commits when set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Import history modal now has a Limit field — restricts import to
the N most recent commits. Preview updates live as limit changes.

Reasoning column truncates in collapsed rows, shows full text in
expanded detail section.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The trigger commit in a batch evaluation was not getting batch_id set,
so it appeared outside the batch group in the UI. Now all decisions in
a batch (deferred + trigger) share the same batch_id.

Also fix React crash when content_source is an object (ContextSourceSpec)
instead of a string — render the types array as comma-separated text.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Paid tier guidance now explicitly tells the drafter to take advantage
of the 25K char limit with rich, detailed content. Prefer single
flowing posts over threads. Intro posts emphasize substance and depth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Also moves "Agent-first CLI equivalents" from backlog to recurring checks
in DOC_STATUS.md and regenerates CLI docs.

https://claude.ai/code/session_01VPEn1WCxtvWPtcjAd5wphk
Ensures every CLI command that has a --yes flag also documents it
in the docstring examples, so agents and CI can discover the
non-interactive bypass from --help output.

https://claude.ai/code/session_01VPEn1WCxtvWPtcjAd5wphk
…ation

Move batch membership marking from post-hoc (after _run_targets_path)
to Phase A (cycle creation) and Phase E (decision creation). Deferred
decisions show "Batched" immediately instead of staying as "Processing"
until drafting finishes.

- Add TargetsPathResult dataclass (exit_code, cycle_id, decision_id)
- _run_targets_path returns TargetsPathResult instead of int
- Deferred decisions get batch_id in Phase A (cycle creation)
- Trigger decision gets batch_id in Phase E (after decision insert)
- Remove mark_decisions_processing from evaluate_batch (redundant)
- Delete post-hoc batch marking block (get_recent_cycles re-query)
- Add error recovery: clear batch_id on pipeline failure
- Add DECIDING pipeline stage (decision creation + arc + queue actions)
- Replace _val() with enum_value() from parsing.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore(docs-maintenance): maintenance run 2026-03-31
Auto-discovery (brief creation + stale refresh) was inline in run_trigger,
missing from batch evaluate, CLI batch-evaluate, and scheduler drain.
Extracted as ensure_project_brief() called from all four evaluation paths.

Fresh projects now get a brief regardless of evaluation entry point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Background tasks can now emit named stages via ops.emit_task_stage().
Frontend useBackgroundTasks tracks current_stage/stage_label in-memory
from SSE events. Per-row buttons show stage labels with timers during
batch evaluation and retrigger.

Backend:
- emit_task_stage() in operations.py (thin wrapper over emit_data_event)
- task_id on TriggerContext + run_trigger for stage tracking
- Pipeline stages (ANALYZING, EVALUATING, DECIDING, DRAFTING) emit both
  pipeline events (toasts) and task stage events (per-task progress)
- Mutable holder pattern for passing task_id to blocking closures

Frontend:
- DataChangeEvent gains stage/stage_label fields
- BackgroundTask gains current_stage/stage_label/stage_started_at
- useBackgroundTasks handles "stage" action in-memory, preserves during refresh
- Per-row buttons use batch task stage for label and timer

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The "N pending" / "N posted" text in the evaluation cycles status
column is now a clickable link to the drafts page filtered by project.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rom commit column

Reasoning was duplicated in the expanded commit detail section and
truncated by max-w-[300px] in the reasoning column. Removed the
duplicate box and the width constraint so reasoning displays fully
when the row is expanded.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…truncation

Reasoning was truncated to 500 chars before DB storage — removed the
limit so full per-strategy reasoning is preserved. Added ExpandableText
component (ui/) for table cells that clamp to 3 lines collapsed and
wrap fully when expanded. Applied to reasoning and angle columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Targets with an account but no OAuth tokens (no credentials set up)
were not getting preview_mode=True. Now checks oauth_tokens table —
if the account has no tokens, the draft is in preview mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Preview mode now triggers when a target's account has no OAuth tokens
in the DB (per-account check, not per-platform). Diagnostics also
report preview mode when account exists but has no credentials.

The accounts_with_creds set is built from oauth_tokens at the caller
level (trigger.py for diagnostics, drafting.py for preview_targets)
to keep diagnostic checks as pure functions per CODING_PRACTICES.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 4 bot handler LLM calls (Gatekeeper, Expert escalation, angle
redraft, media spec) now run via _run_background_task instead of
blocking the HTTP/WebSocket response for 5-60s.

Backend:
- handle_message + handle_callback accept optional task_id
- Stage events emitted before each LLM call (routing, thinking,
  redrafting, generating) using locally-scoped connections
- /api/message always returns 202 (all messages go through background)
- /api/callback returns 202 for LLM actions (media_gen_spec), sync
  for non-LLM (approve, reject, schedule)
- WebSocket send_message dispatches to background task with deferred
  ack containing task_id

Frontend:
- sendMessage/sendCallback return types include optional task_id
- button-row.tsx guards against undefined events on 202 responses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
draft-action-panel.tsx: wires useBackgroundTasks for edit_angle and
other text prompts. onTaskCompleted clears pending state and refreshes
draft. Failed tasks show error message.

chat-panel.tsx: tracks chat background tasks via gateway ack envelope
with task_id. Listens for task completion/failure events. Shows toast
on failure. Keeps sending=true until task completes (not on send).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…per, reuse conn

- Fix critical stale closure: chatTaskId changed from useState to useRef
  so the gateway listener always reads the current value
- Extract _dispatch_chat_message() shared by /api/message and WebSocket
- Use db.conn for emit_task_stage in _handle_expert_escalation instead
  of opening a separate connection
- Move generate_id import inside LLM branch of /api/callback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Confirm button and text input disappeared immediately after clicking
because handleTextSubmit cleared textPrompt/submenu in the finally block.
Now the UI stays in loading state until onTaskCompleted fires — the user
sees the Confirm button disabled with actionPending while the LLM works.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iately

promoteDraft() always returns a task_id (LLM pipeline work) but the
handler was ignoring it and clearing loading state in finally. Now
tracks via useBackgroundTasks — loading persists until the promote
task completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/social_hook/models/__init__.py
#	src/social_hook/parsing.py
#	src/social_hook/topics.py
#	src/social_hook/trigger.py
#	src/social_hook/web/server.py
#	tests/test_drafting_adapt.py
#	tests/test_drafting_shared_group.py
#	tests/test_phase6_pipeline.py
Develop added 500-char truncation to _combine_strategy_reasoning().
Our branch deliberately keeps full reasoning — truncation loses
per-strategy context that operators need for decision review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Develop's decomposition of trigger.py into submodules lost our
emit_task_stage calls in evaluate_batch(). These track "analyzing"
and "evaluating" stages for the frontend progress indicator.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Regenerate site-docs/cli/ for --strategy and --branch quickstart flags
- Fix trigger_batch.py:259 mypy error: annotate exit_code as int

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests derived from TARGETS_DESIGN.md cross-referenced with coverage
gaps. Focus on behaviors that catch real bugs, not trivial plumbing.

Interval gating (4 tests):
- Deferral when count < threshold
- Evaluation at threshold
- Minimum clamp (interval=0 → always evaluate)
- No config defaults to evaluate

Topic queue (7 tests):
- Dismissed topics excluded from tag matching
- Dismissed topics not recreated by seeding
- Strategy-scoped seeding (positioning vs code-driven)
- Draft Now accepts uncovered, rejects dismissed
- Granularity gating (routine blocked at low, notable passes)

Evaluator prompt (3 tests):
- Diff excluded when analysis present
- Diff included without analysis
- Empty strategies raises ConfigError

Config validation (3 tests):
- Invalid topic_granularity raises ConfigError
- Valid values accepted
- Default is "low"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: ✨ targets system — full implementation with two-stage evaluation, topic queue, and UI
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 1, 2026

@nj-io nj-io merged commit 6965406 into main Apr 3, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants