- New Cost_Tracker class: logs every API call (model, tokens, cost), enforces
monthly budgets with hard-stop mode, provides aggregated reporting
- New llm_costs database table with indexes on created_at, model, operation
- Modified LLM class: call_openrouter_with_usage() extracts token usage from
OpenRouter responses; budget gate checks before each API call
- Admin settings: monthly budget (USD), enforcement mode (disabled/warn/hard-stop),
cost log retention period
- Admin cost dashboard: budget progress bar, daily cost chart (Chart.js),
per-model and per-operation breakdowns, recent API call log
- PHPUnit tests: 16 tests covering cost calculation, pricing lookups,
budget enforcement logic, and model validation
- Updated ARCHITECTURE.md with new table schema, data flow, and key decisions
- Updated CONVENTIONS.md with "how to add cost tracking for a new operation"
- Version bump to 2.4.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WordPress coding standards require tab indentation. All 12 PHP files
in includes/, admin/, and public/ were using 4-space indentation.
Converted to tabs to pass the Generic.WhiteSpace.DisallowSpaceIndent
PHPCS rule. Whitespace-only change — no logic modifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security (7 fixes):
- SEC-001: Use wpdb->prepare() for COUNT query in REST API
- SEC-002: Set httponly=true on session cookie to prevent XSS
- SEC-003: Mask API keys in admin settings (show last 4 chars only)
- SEC-004: Check capability before nonce in manual fetch handler
- SEC-005: Validate redirect domains to prevent open redirects
- SEC-006: Escape all article fields before REST API response
- SEC-007: Add capability check to AJAX summary handler
Infrastructure (7 fixes):
- WPS-002: Clear cron schedule on plugin deactivation
- WPS-003: Bundle Chart.js locally instead of CDN without SRI
- WPS-004: Add uninstall.php for clean plugin removal
- DB-001: Add foreign key constraints with CASCADE deletes
- DB-002: Clamp pagination to valid page range
- CI-001: Use StrictHostKeyChecking=accept-new in deploy
- CI-002: Add security scanning step to CI pipeline
- CI-003: Add rollback workflow via workflow_dispatch
Performance & Quality (8 fixes):
- PHP-001: Validate widget $instance as array before access
- PHP-002: Return null from get_client_ip() for invalid IPs
- PHP-003: Process RSS feeds in chunked batches of 10
- PHP-004: Wire up clear_article_cache() in fetch cycle
- PERF-001: Cache source names in transient for strip_source_suffix
- PERF-002: Add server-side transient caching for REST API
- ARCH-002: Make LLM API URL configurable via WordPress options
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deploy workflow reads from the repo root (admin/, includes/, public/),
not the peptide-news-plugin/ subdirectory. Applied the same changes:
- Removed Tag component and tag rendering from ArticleCard
- Removed all tag-related CSS (variables, styles, skeleton, responsive)
- Added strip_source_suffix() to REST API for cleaning source names
from titles/summaries with case-insensitive and domain-based matching
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Complete CSS rewrite implementing a clean, modern design:
- Inter font family with refined typography scale (22px titles, 15px body)
- CSS custom properties for full light/dark mode theming
- Card-based article layout with 8px radius, subtle shadows, hover lift
- Generous spacing and whitespace for premium feel (900px max-width)
- Source badges with background treatment, blue-tinted tag pills
- Staggered skeleton loading animations
- WCAG AA accessible focus states and contrast ratios
- Responsive breakpoints at 480px, 768px, 1024px
- Reduced motion and high contrast media query support
- Version bumped to 2.2.0
Design approved by Design Director (8.5/10), QA verified all
28 .pn-* class names preserved with zero React component changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove CSS line-clamp that was truncating summaries to 3 lines (2 on
mobile). The AI summary is 4 sentences and should be displayed in full.
Also add decodeEntities() to clean up and ' artifacts in
titles and summaries returned by the REST API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Append &_=${Date.now()} to the fetch URL in fetchArticles() to bypass LiteSpeed and CDN REST API caches. The in-memory JS apiCache key remains unchanged so client-side deduplication still works.
Remove all AI thumbnail generation infrastructure (too expensive,
poor results) and rewrite the public-facing article feed as a React
SPA mounted via WordPress shortcode.
Changes:
- Remove thumbnail generation, OG scraping, and image download code
from fetcher, LLM, and admin classes
- Replace server-rendered PHP templates with React component
(PeptideNewsFeed.jsx) using WordPress-bundled React/ReactDOM
- Add Babel build pipeline for JSX transpilation
- Update REST API to serve ai_summary instead of thumbnail fields
- Redesign public CSS: clean card layout, loading skeletons,
dark mode, responsive, no thumbnail imagery
- Remove dead admin settings (llm_image_model, thumbnail_fallback)
- Fix call_openrouter() visibility for Content Filter cross-class use
- Bump version to 1.3.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DuckDuckGo's HTML search endpoint now requires vqd session tokens,
making server-side search-based URL resolution unreliable. Simplified
the thumbnail pipeline:
- OG scrape only for non-aggregator URLs (direct publisher pages)
- AI image generation via OpenRouter for aggregator articles (Google News)
- Added Image Model settings field in admin UI
- Added thumbnail_local column for AI-generated images
- Updated public/admin display to check thumbnail_local as fallback
- Removed all DuckDuckGo search code (find_article_url_via_search,
extract_ddg_url, resolve_article_for_scraping)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Google News RSS stores encrypted article URLs that cannot be resolved
server-side (JS SPA, no HTTP redirects, encrypted base64 IDs). Replace
the failed decode/parse strategies with DuckDuckGo search-based lookup
using article title + publisher name to find actual article pages, then
scrape OG images from those pages.
- Add find_article_url_via_search() using DuckDuckGo HTML endpoint
- Add resolve_article_for_scraping() combining redirect + search fallback
- Mark failed lookups with '_no_image' sentinel to avoid repeated retries
- Backfill button resets sentinels to allow manual re-scraping
- Clean up verbose debug instrumentation from prior attempts
- Update frontend to handle '_no_image' sentinel gracefully
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses QA Agent 2 findings:
- Add :focus outline on title and thumbnail links for keyboard navigation
- Wrap excerpt paragraph in conditional to avoid empty <p> when both
ai_summary and excerpt are missing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrites public CSS to match actual .pn-* class names from the PHP
template and changes the default shortcode layout from card/grid to a
horizontal list: fixed-width thumbnail on the left, title + summary on
the right. Includes compact variant for widgets and responsive
stacking on small screens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Peptide_News_Loader class was referenced by class-peptide-news.php
but never committed, causing a fatal error on plugin activation.
Also adds public/css/public-style.css which is enqueued by the public
class.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>