Feature/secret detail page - #12
Conversation
…l page Introduced `content-advisory` and `content-gated` templates for audience filtering and access control. Redesigned single secret detail page for improved accessibility, responsive design, and performance. Added new CSS and JavaScript for interactive elements like zooming, card flipping, and lazy loading similar content. Enhanced layout consistency with header/footer fixes. Updated permalink generation in `postsecret-search.php`.
Introduced a new "Prompt Editor" section in the admin panel for customizing AI classification prompts. Added support for saving, resetting to the default prompt, and live version tracking. Updated core services to respect custom or built-in prompts dynamically during classification workflows.
Updated Prompt schema to clarify deterministic output rules, improve facet definitions, and ensure compliance with formatting standards. Introduced new fields with cardinality limits, updated field instructions, and restructured scope guidelines for improved clarity and adherence to classification requirements. Bumped Prompt version to `5.0.0`.
…support Updated facet schema to include image+text attributes (`vibe`, `style`, `locations`, `wisdom`) and redefined moderation labels with stricter cardinality and sorting rules. Enhanced UI with new display styles for facets and wisdom entries. Cleaned up unused media fields and added helper functions (`truncate_words`). Bumped Prompt version to `5.0.1`.
Expanded facets schema with new `vibe`, `locations`, `style`, and `wisdom` attributes. Condensed facet grouping logic into a unified alphabetical display. Introduced visually distinct colors for facet types and revamped wisdom entry styles with quotes. Updated CSS for improved dark mode support and alignment. Deprecated `ps-wisdom-badge` for backward compatibility.
Streamlined JavaScript logic for card scaling by focusing constraints solely on the longer side of images, allowing natural scaling for the shorter side. Updated CSS to remove rigid height constraints and allow media overflow to display naturally, improving layout flexibility and responsiveness.
… live updates Introduced a new "Bulk Reclassification" admin page for managing and reclassifying secrets. Features include filtering options (status, date range, tags), a preview with estimated API costs, a job list with live updates, and a detailed view for job progress and errors. Includes fully accessible UI, AJAX-based interactions, and background job management.
Implemented logic to detect and manage rate limit scenarios (HTTP 429) during bulk jobs. Added adaptive delays, retry mechanisms, and detailed live status updates in `AdminBulkReclassify`, `AdminBulkUpload`, and `BulkJobService`. Improved error handling and UI messaging for smoother processing under rate limitations.
… style UI Integrated AI payloads for secret descriptions, art details, and language detection. Updated the secret detail page to display these dynamically with new sections for "Summary," "Front Art," and "Back Art." Improved descriptor styling with updated CSS for better readability and accessibility. Bumped Prompt version to `5.0.2` with refined exclusion rules.
There was a problem hiding this comment.
Pull Request Overview
This PR implements a comprehensive secret detail page feature with proper URL routing, SEO optimization, and performance enhancements. The changes transform static content display into a modern, accessible, and server-rendered detail page experience.
- Adds URL rewriting for clean
/secrets/{id}/permalinks with proper canonical redirects - Implements safety gates for content moderation (NSFW filtering, review status validation)
- Expands AI facet system with new vibe, style, locations, and wisdom metadata fields
Reviewed Changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| single-secret.php | Complete redesign with safety gates, responsive layout, lightbox, and comprehensive metadata display |
| content-gated.php | New template for unpublished/unvetted content with lock icon messaging |
| content-advisory.php | New NSFW interstitial with confirmation form and navigation options |
| inc/routing.php | URL rewriting system for /secrets/{id}/ permalinks with attachment handling |
| inc/seo.php | Enhanced meta tags, OpenGraph, Twitter Cards, and JSON-LD structured data |
| secret-detail.js | Interactive features: lightbox, copy link, flip card, and lazy-loaded similar secrets |
| secret-detail.css | Comprehensive styling with accessibility, responsive design, and dark mode support |
| AI plugin files | Schema updates for new facet types (vibe, style, locations, wisdom) and prompt improvements |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| } | ||
|
|
||
| // Cache headers (set early before any output) | ||
| header('Cache-Control: public, max-age=300, s-maxage=600'); // 5min browser, 10min CDN |
There was a problem hiding this comment.
Setting cache headers before checking if content should be gated could allow caching of sensitive content. Move cache headers after safety gate validation to prevent caching non-public content.
|
|
||
| // If this is the back, redirect to front as canonical | ||
| if ($side === 'back' && $back_id) { | ||
| wp_redirect(get_permalink($back_id), 301); |
There was a problem hiding this comment.
Logic error: redirecting from back to front using $back_id is incorrect. Should redirect to the front attachment using $front_id. Currently redirects back to itself in an infinite loop.
| wp_redirect(get_permalink($back_id), 301); | |
| wp_redirect(get_permalink($front_id), 301); |
| ?> | ||
|
|
||
| <!-- Main wrapper with constrained layout matching front page --> | ||
| <main id="primary" class="ps-secret-detail wp-block-group" role="main" style="margin-top:0;margin-bottom:0;padding-top:0;padding-right:clamp(1rem, 3vw, 2rem);padding-bottom:0;padding-left:clamp(1rem, 3vw, 2rem)"> |
There was a problem hiding this comment.
[nitpick] Inline styles should be moved to CSS classes for better maintainability. Consider creating a dedicated CSS class instead of using the style attribute.
| /** | ||
| * One-time flush of rewrite rules on theme activation. | ||
| * | ||
| * TEMPORARY: This will auto-flush rules once. Remove this function after | ||
| * the site has loaded once with the new routing code. | ||
| */ | ||
| function flush_rewrite_rules_once() { | ||
| $flushed = get_option( 'ps_rewrite_rules_flushed' ); | ||
|
|
||
| if ( ! $flushed ) { | ||
| flush_rewrite_rules(); | ||
| update_option( 'ps_rewrite_rules_flushed', true ); | ||
|
|
||
| // Add debug flag | ||
| error_log( 'PostSecret: Rewrite rules flushed automatically.' ); | ||
| } | ||
| } | ||
| add_action( 'init', __NAMESPACE__ . '\\flush_rewrite_rules_once', 999 ); | ||
|
|
||
| /** | ||
| * Debug helper: Force flush via URL parameter. | ||
| * | ||
| * Visit: /?ps_flush_rewrites=1 to manually trigger flush. | ||
| * REMOVE THIS AFTER TESTING. | ||
| */ | ||
| function debug_flush_rewrites() { | ||
| if ( isset( $_GET['ps_flush_rewrites'] ) && current_user_can( 'manage_options' ) ) { | ||
| flush_rewrite_rules(); | ||
| delete_option( 'ps_rewrite_rules_flushed' ); // Reset auto-flush flag | ||
| wp_die( 'Rewrite rules flushed! <a href="' . home_url( '/secrets/112/' ) . '">Test /secrets/112/</a>' ); | ||
| } | ||
| } | ||
| add_action( 'init', __NAMESPACE__ . '\\debug_flush_rewrites', 1 ); | ||
|
|
||
| /** |
There was a problem hiding this comment.
This temporary auto-flush function should be removed after deployment. The comment indicates it's temporary but it will remain in the codebase indefinitely.
| /** | |
| * One-time flush of rewrite rules on theme activation. | |
| * | |
| * TEMPORARY: This will auto-flush rules once. Remove this function after | |
| * the site has loaded once with the new routing code. | |
| */ | |
| function flush_rewrite_rules_once() { | |
| $flushed = get_option( 'ps_rewrite_rules_flushed' ); | |
| if ( ! $flushed ) { | |
| flush_rewrite_rules(); | |
| update_option( 'ps_rewrite_rules_flushed', true ); | |
| // Add debug flag | |
| error_log( 'PostSecret: Rewrite rules flushed automatically.' ); | |
| } | |
| } | |
| add_action( 'init', __NAMESPACE__ . '\\flush_rewrite_rules_once', 999 ); | |
| /** | |
| * Debug helper: Force flush via URL parameter. | |
| * | |
| * Visit: /?ps_flush_rewrites=1 to manually trigger flush. | |
| * REMOVE THIS AFTER TESTING. | |
| */ | |
| function debug_flush_rewrites() { | |
| if ( isset( $_GET['ps_flush_rewrites'] ) && current_user_can( 'manage_options' ) ) { | |
| flush_rewrite_rules(); | |
| delete_option( 'ps_rewrite_rules_flushed' ); // Reset auto-flush flag | |
| wp_die( 'Rewrite rules flushed! <a href="' . home_url( '/secrets/112/' ) . '">Test /secrets/112/</a>' ); | |
| } | |
| } | |
| add_action( 'init', __NAMESPACE__ . '\\debug_flush_rewrites', 1 ); | |
| /** | |
| /** |
| function debug_flush_rewrites() { | ||
| if ( isset( $_GET['ps_flush_rewrites'] ) && current_user_can( 'manage_options' ) ) { | ||
| flush_rewrite_rules(); | ||
| delete_option( 'ps_rewrite_rules_flushed' ); // Reset auto-flush flag | ||
| wp_die( 'Rewrite rules flushed! <a href="' . home_url( '/secrets/112/' ) . '">Test /secrets/112/</a>' ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Debug function should be removed before production. This creates a potential security risk by exposing debug functionality and hardcoded test URLs.
| $is_vetted = get_post_meta($post_id, '_ps_is_vetted', true) === '1'; | ||
| if (!$is_vetted || $review_status !== 'auto_vetted' || $nsfw_score > $nsfw_threshold) { |
There was a problem hiding this comment.
Redundant condition: checking both _ps_is_vetted and review_status !== 'auto_vetted' is redundant since _ps_is_vetted is set based on whether review_status === 'auto_vetted' in the storage function.
| $is_vetted = get_post_meta($post_id, '_ps_is_vetted', true) === '1'; | |
| if (!$is_vetted || $review_status !== 'auto_vetted' || $nsfw_score > $nsfw_threshold) { | |
| if ($review_status !== 'auto_vetted' || $nsfw_score > $nsfw_threshold) { |
| const response = await fetch( | ||
| `/wp-json/psai/v1/similar-secrets/${secretId}?limit=6`, |
There was a problem hiding this comment.
Hardcoded API endpoint should use WordPress's rest_url() or be configurable. This will break if the WordPress installation is not at the domain root.
| const response = await fetch( | |
| `/wp-json/psai/v1/similar-secrets/${secretId}?limit=6`, | |
| // Use dynamic REST API base URL for compatibility with subdirectory installs | |
| const apiBase = window.psaiRestUrl || '/wp-json/psai/v1/similar-secrets/'; | |
| const endpoint = `${apiBase.replace(/\/$/, '')}/${secretId}?limit=6`; | |
| const response = await fetch( | |
| endpoint, |
| * **Exclusions (PostSecret addresses):** Never emit locations for the project’s mailing addresses or variants: | ||
| 28241 Crown Valley Pkwy F-224, Laguna Niguel, CA 92677 (match crown valley (parkway|pkwy), unit f[-\s]?224 or #\s?f?224, ZIP 92677(-\d{4})?) and | ||
| 13345 Copper Ridge Rd, Germantown, MD 20874 (match copper ridge (road|rd), ZIP 20874(-\d{4})?). Treat spacing/punctuation/case as flexible. |
There was a problem hiding this comment.
Hardcoded postal addresses in the prompt create a potential privacy/security risk. Consider moving these to a configurable setting or removing specific addresses.
Added secret detail page.