Compare commits

...

3 Commits

Author SHA1 Message Date
peptiderepo
98629f3ef8 ci(audit): retry PR-association check to stop false positives on squash-merges
listPullRequestsAssociatedWithCommit is eventually-consistent after a squash
merge; the single immediate call intermittently flagged legit merges as
'direct push to main'. Now polls up to 5x/10s before opening an issue.

Agent-Session: cto audit-race-fix
2026-06-09 13:38:29 +07:00
peptiderepo
844951b268 feat(front-page): remove Science Feed card + 3-item news teaser (v2.2.2)
Merges PR #55. Science Feed removed from module grid (5 cards). Inline news feed replaced with 3-item teaser + CTA. Version 2.2.1 → 2.2.2.
2026-04-29 14:35:03 +07:00
peptiderepo
2b23bb7e2a feat: RCT inline abbreviation tooltip on verdict legend strip (v2.2.1) (#53)
CI green (PHP Lint 8.1/8.2/8.3 ✓, PHPCS ✓, PHPUnit 8.1/8.2/8.3 ✓). QA: PASS.
2026-04-29 13:29:04 +07:00
9 changed files with 311 additions and 29 deletions

View File

@@ -29,12 +29,22 @@ jobs:
with:
script: |
const sha = context.sha;
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: sha,
});
const merged = prs.find(p => p.merged_at);
// Squash-merge -> PR association is eventually-consistent; poll briefly
// before concluding this was a direct push (avoids false positives on merges).
async function findMergedPR() {
for (let attempt = 1; attempt <= 5; attempt++) {
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: sha,
});
const m = prs.find(p => p.merged_at);
if (m) return m;
if (attempt < 5) await new Promise(r => setTimeout(r, 10000));
}
return null;
}
const merged = await findMergedPR();
if (merged) {
core.info(`OK: commit ${sha.substring(0,7)} came from PR #${merged.number}`);
return;

View File

@@ -1,5 +1,42 @@
# Peptide Starter Theme - Changelog
## [2.2.2] - 2026-04-29 — Front page layout restructure
### Changed
- `template-parts/module-cards.php`: Removed Science Feed from the module card
grid. The grid is for interactive tools only; news is content. Grid is now
5 cards: Peptides, Calculator, Protocol Builder, Tracker, Subject Log. No card
in this set goes inactive-dimmed due to a plugin registration check.
- `front-page.php`: Replaced the full `[peptide_news]` inline feed with a
3-item teaser row (`[peptide_news count="3"]`) and a "View all research →"
CTA linking to `/news`. Reduces page weight and drives traffic to the
dedicated news page.
## [2.2.1] - 2026-04-29 — RCT inline abbreviation tooltip
### Added
- `.pr-abbr` tap-target on the term "RCT" in the Established verdict card on
the verdict legend strip (homepage). Tapping/clicking opens a small inline
popover with the full definition: "Randomized Controlled Trial — the gold
standard for clinical evidence." Closes on outside click, Escape key, or
focus leaving the element.
- `assets/js/abbr-tooltip.js` — new JS module (~130 lines) for popover open/
close, outside-click dismissal, keyboard accessibility (Enter/Space/Escape),
and ARIA attributes (`aria-expanded`, `aria-describedby`, `role="tooltip"`).
Enqueued on front page only (`is_front_page()`).
- `peptide_starter_render_desc( string $text, array $abbrs ): string` helper in
`inc/verdict-helpers.php`. Builds safe HTML from `esc_html`/`esc_attr`
components; verdict taxonomy items carry an optional `abbrs` map.
- `.pr-abbr` and `.pr-abbr-popover` CSS in `style.css` — dashed underline
affordance, fade-in animation, dark-mode-safe surface variables.
### Fixed
- `PEPTIDE_STARTER_VERSION` constant in `functions.php` was stale at
`2.0.0-alpha.3`; realigned to `2.2.1` to match `style.css` Version header.
## [2.1.8] - 2026-04-28 — Homepage verdict section spacing fix
### Fixed

134
assets/js/abbr-tooltip.js Normal file
View File

@@ -0,0 +1,134 @@
/**
* abbr-tooltip.js — Inline abbreviation popover for verdict legend screen.
*
* What: Opens a small popover anchored to any .pr-abbr element when tapped,
* clicked, or activated via keyboard. Closes on outside click or Escape.
* Who triggers: Enqueued by functions.php on is_front_page() requests only.
* Depends on: .pr-abbr elements in the DOM with data-def attributes; CSS in
* style.css (.pr-abbr-popover).
*
* @see inc/verdict-helpers.php — peptide_starter_render_desc() emits .pr-abbr elements
* @see style.css — .pr-abbr, .pr-abbr-popover visual styles
* @package peptide-starter
*/
( function () {
'use strict';
var POPOVER_CLASS = 'pr-abbr-popover';
var ACTIVE_CLASS = 'pr-abbr--active';
var activePopover = null;
var activeAbbr = null;
/**
* Remove the currently open popover, if any.
*
* @return {void}
*/
function closePopover() {
if ( activePopover ) {
activePopover.remove();
activePopover = null;
}
if ( activeAbbr ) {
activeAbbr.classList.remove( ACTIVE_CLASS );
activeAbbr.removeAttribute( 'aria-expanded' );
activeAbbr = null;
}
}
/**
* Create and position a popover anchored to the given abbr element.
*
* @param {HTMLElement} abbr - The .pr-abbr element that was activated.
* @return {void}
*/
function openPopover( abbr ) {
closePopover();
var def = abbr.getAttribute( 'data-def' );
if ( ! def ) {
return;
}
var popover = document.createElement( 'div' );
popover.className = POPOVER_CLASS;
popover.textContent = def;
popover.setAttribute( 'role', 'tooltip' );
popover.setAttribute( 'id', POPOVER_CLASS + '-' + Date.now() );
document.body.appendChild( popover );
// Position below the abbr, clamped to viewport width.
var rect = abbr.getBoundingClientRect();
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
var top = rect.bottom + scrollTop + 6;
var left = rect.left + scrollLeft;
var maxLeft = window.innerWidth - popover.offsetWidth - 16;
popover.style.top = top + 'px';
popover.style.left = Math.max( 8, Math.min( left, maxLeft ) ) + 'px';
abbr.classList.add( ACTIVE_CLASS );
abbr.setAttribute( 'aria-expanded', 'true' );
abbr.setAttribute( 'aria-describedby', popover.id );
activePopover = popover;
activeAbbr = abbr;
}
/**
* Toggle the popover for an abbr element (open if closed, close if open).
*
* @param {HTMLElement} abbr - The .pr-abbr element that was activated.
* @return {void}
*/
function togglePopover( abbr ) {
if ( activeAbbr === abbr ) {
closePopover();
} else {
openPopover( abbr );
}
}
// -------------------------------------------------------------------------
// Event listeners
// -------------------------------------------------------------------------
document.addEventListener( 'click', function ( e ) {
var abbr = e.target.closest( '.pr-abbr' );
if ( abbr ) {
e.stopPropagation();
togglePopover( abbr );
return;
}
// Click outside any abbr — close.
closePopover();
} );
document.addEventListener( 'keydown', function ( e ) {
if ( e.key === 'Escape' ) {
closePopover();
return;
}
if ( e.key === 'Enter' || e.key === ' ' ) {
var abbr = e.target.closest( '.pr-abbr' );
if ( abbr ) {
e.preventDefault();
togglePopover( abbr );
}
}
} );
// Close when focus leaves the popover+abbr pair entirely.
document.addEventListener( 'focusin', function ( e ) {
if (
activeAbbr &&
! activeAbbr.contains( e.target ) &&
( ! activePopover || ! activePopover.contains( e.target ) )
) {
closePopover();
}
} );
} )();

View File

@@ -57,7 +57,7 @@
echo '<h3>' . esc_html__( 'Our Method', 'peptide-starter' ) . '</h3>';
echo '<ul>';
echo '<li><a href="' . esc_url( home_url( '/how-we-review-peptides' ) ) . '">' . esc_html__( 'How We Review Peptides', 'peptide-starter' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/our-methodology' ) ) . '">' . esc_html__( 'Our Methodology', 'peptide-starter' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/our-methodology' ) ) . '">' . esc_html__( 'Our Methodology', 'peptide-starter' ) . '</a></li>';
echo '<li><a href="' . esc_url( home_url( '/about' ) ) . '">' . esc_html__( 'Editorial Standards', 'peptide-starter' ) . '</a></li>';
echo '</ul>';
}

View File

@@ -101,7 +101,7 @@ $featured_monograph_ids = array( 211, 36, 177 );
<span class="pr-verdict__glyph" aria-hidden="true"><?php echo esc_html( $v['glyph'] ); ?></span>
<span class="pr-verdict__label"><?php echo esc_html( $v['label'] ); ?></span>
</span>
<p class="hero-verdict-explainer__desc"><?php echo esc_html( $v['description'] ); ?></p>
<p class="hero-verdict-explainer__desc"><?php echo peptide_starter_render_desc( $v['description'], $v['abbrs'] ?? array() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- output is built from esc_html/esc_attr components inside render_desc(). ?></p>
</div>
<?php endforeach; ?>
</div>
@@ -204,7 +204,7 @@ $featured_monograph_ids = array( 211, 36, 177 );
</section>
<?php endif; ?>
<!-- Research Modules Grid (6 cards) -->
<!-- Research Modules Grid (5 cards) -->
<?php get_template_part( 'template-parts/module', 'cards' ); ?>
<!-- PRAutoBlogger Posts Widget -->
@@ -214,19 +214,22 @@ $featured_monograph_ids = array( 211, 36, 177 );
}
?>
<!-- News Feed Section -->
<!-- News Teaser Section — 3 items + "View all" CTA -->
<?php
if ( shortcode_exists( 'peptide_news' ) ) {
?>
<section class="news-feed-section">
<section class="news-teaser-section">
<div class="ps-container">
<div class="news-feed-header">
<h2 class="news-feed-title"><?php esc_html_e( 'What\'s new', 'peptide-starter' ); ?></h2>
<p class="news-feed-description">
<?php esc_html_e( 'Recent peptide research, regulatory shifts, and synthesis news. New posts weekly.', 'peptide-starter' ); ?>
</p>
<div class="news-teaser-header">
<div>
<h2><?php esc_html_e( 'Latest Peptide Research', 'peptide-starter' ); ?></h2>
<p><?php esc_html_e( 'Recent publications from leading research sources.', 'peptide-starter' ); ?></p>
</div>
<a href="<?php echo esc_url( home_url( '/news' ) ); ?>" class="ps-btn ps-btn-secondary">
<?php esc_html_e( 'View all research →', 'peptide-starter' ); ?>
</a>
</div>
<?php echo do_shortcode( '[peptide_news]' ); ?>
<?php echo do_shortcode( '[peptide_news count="3"]' ); ?>
</div>
</section>
<?php
@@ -236,3 +239,4 @@ $featured_monograph_ids = array( 211, 36, 177 );
</main>
<?php get_footer(); ?>

View File

@@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
}
// Theme constants.
define( 'PEPTIDE_STARTER_VERSION', '2.0.0-alpha.3' );
define( 'PEPTIDE_STARTER_VERSION', '2.2.2' );
define( 'PEPTIDE_STARTER_DIR', get_template_directory() );
define( 'PEPTIDE_STARTER_URI', get_template_directory_uri() );
@@ -88,6 +88,11 @@ function peptide_starter_scripts() {
wp_enqueue_script( 'peptide-starter-theme', PEPTIDE_STARTER_URI . '/assets/js/theme.js', array(), PEPTIDE_STARTER_VERSION, true );
wp_enqueue_script( 'peptide-starter-settings-panel', PEPTIDE_STARTER_URI . '/assets/js/settings-panel.js', array(), PEPTIDE_STARTER_VERSION, true );
// Abbreviation popovers — only needed on the front page verdict legend strip.
if ( is_front_page() ) {
wp_enqueue_script( 'peptide-starter-abbr-tooltip', PEPTIDE_STARTER_URI . '/assets/js/abbr-tooltip.js', array(), PEPTIDE_STARTER_VERSION, true );
}
wp_localize_script(
'peptide-starter-theme',
'peptideStarterData',

View File

@@ -19,7 +19,7 @@
*
* Canonical order matches CMO brief §5.1 and the 5-state enum in verdict-meta.php.
*
* @return array[] Each element: state (string), glyph (string), label (string), description (string).
* @return array[] Each element: state (string), glyph (string), label (string), description (string), abbrs (array, optional).
*/
function peptide_starter_get_verdict_taxonomy() {
return array(
@@ -28,6 +28,10 @@ function peptide_starter_get_verdict_taxonomy() {
'glyph' => '✓',
'label' => 'Established',
'description' => 'Strong RCT evidence; widely prescribed or validated.',
// abbrs: terms in description that should render as inline tap-targets.
'abbrs' => array(
'RCT' => 'Randomized Controlled Trial — the gold standard for clinical evidence. Participants are randomly assigned to treatment or control groups to measure real effect.',
),
),
array(
'state' => 'promising',
@@ -88,3 +92,46 @@ function peptide_starter_get_verdict_config() {
),
);
}
/**
* Renders a description string with optional inline abbreviation tap-targets.
*
* What: Builds safe HTML for a verdict card description, replacing known
* abbreviation terms with <abbr> elements that trigger a tooltip.
* Who calls it: front-page.php verdict explainer loop.
* Depends on: Nothing — pure string transformation.
*
* The output is intentionally built from escaped components (not from raw $text)
* so we never expose unescaped user or data-layer content. Each substitution is:
* esc_html( $text ) → str_replace( literal_term, safe_abbr_html )
* This is safe because the abbr tag, class, and data-def are all hardcoded or
* escaped here, and the term key is a literal constant from our own data array.
*
* @param string $text Plain-text description string. Will be HTML-escaped.
* @param array $abbrs Associative array of term => definition. Keys are literal
* strings (e.g. 'RCT'). Values are plain-text definitions.
* @return string Safe HTML string suitable for echo without further escaping.
*
* @see inc/verdict-helpers.php — $abbrs data lives in peptide_starter_get_verdict_taxonomy()
* @see assets/js/abbr-tooltip.js — JS that handles tap/click to show the popover
* @see style.css — .pr-abbr and .pr-abbr-popover styles
*/
function peptide_starter_render_desc( string $text, array $abbrs ): string {
$html = esc_html( $text );
foreach ( $abbrs as $term => $definition ) {
$safe_term = esc_html( $term );
$safe_def = esc_attr( $definition );
$abbr_html = sprintf(
'<abbr class="pr-abbr" data-def="%s" tabindex="0" role="button" aria-label="%s: %s">%s</abbr>',
$safe_def,
$safe_term,
$safe_def,
$safe_term
);
// Replace only the first occurrence — abbreviations should not stack.
$html = preg_replace( '/' . preg_quote( $safe_term, '/' ) . '/', $abbr_html, $html, 1 );
}
return $html;
}

View File

@@ -4,7 +4,7 @@ Theme URI: https://github.com/peptiderepo/peptide-starter
Description: Premium, accessible WordPress theme for scientific peptide reference database
Author: Peptide Repo
Author URI: https://peptiderepo.com
Version: 2.2.0
Version: 2.2.2
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: peptide-starter
@@ -3410,3 +3410,55 @@ html[data-theme="dark"] .ps-verify-required {
grid-template-columns: repeat(5, 1fr);
}
}
/* ==========================================================================
Abbreviation tooltip (.pr-abbr / .pr-abbr-popover)
Inline tap-targets for glossary terms in the verdict legend strip.
@see inc/verdict-helpers.php — peptide_starter_render_desc() emits .pr-abbr
@see assets/js/abbr-tooltip.js — JS popover logic
========================================================================== */
.pr-abbr {
border-bottom: 1px dashed currentColor;
cursor: help;
text-decoration: none;
white-space: nowrap;
/* Slightly muted so the dashed line reads as "interactive" not "broken link". */
opacity: 0.85;
transition: opacity 0.15s ease;
}
.pr-abbr:hover,
.pr-abbr:focus,
.pr-abbr--active {
opacity: 1;
outline: none;
}
.pr-abbr-popover {
position: absolute;
z-index: 9999;
max-width: min( 280px, calc( 100vw - 32px ) );
padding: 0.6rem 0.85rem;
border-radius: 6px;
font-size: 0.8125rem;
line-height: 1.5;
/* Use theme surface colours so dark-mode inherits correctly. */
background-color: var(--color-surface-elevated, #1e1e2e);
color: var(--color-text-primary, #e2e2f0);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
/* Subtle fade-in. */
animation: pr-popover-in 0.12s ease forwards;
}
@keyframes pr-popover-in {
from {
opacity: 0;
transform: translateY( -4px );
}
to {
opacity: 1;
transform: translateY( 0 );
}
}

View File

@@ -2,7 +2,7 @@
/**
* Template Part: Research Module Cards
*
* Renders a 6-card grid of research modules (tools/sections) for the front page.
* Renders a 5-card grid of research modules (tools/sections) for the front page.
* Each card checks if the target plugin is active; inactive cards are dimmed.
* Cards with 'coming_soon' => true display a "Coming soon" badge overlaid in
* the top-right corner — layered on top of the --inactive visual treatment.
@@ -58,14 +58,6 @@ $modules = array(
'available' => shortcode_exists( 'peptide_tracker' ),
'coming_soon' => true,
),
array(
'title' => __( 'Science Feed', 'peptide-starter' ),
'description' => __( 'What\'s new in peptide research. Filtered for what matters, weekly.', 'peptide-starter' ),
'url' => '/news',
'icon' => '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 22h16a2 2 0 002-2V4a2 2 0 00-2-2H8a2 2 0 00-2 2v16a2 2 0 01-2 2zm0 0a2 2 0 01-2-2v-9c0-1.1.9-2 2-2h2"/><line x1="10" y1="6" x2="18" y2="6"/><line x1="10" y1="10" x2="18" y2="10"/><line x1="10" y1="14" x2="14" y2="14"/></svg>',
'available' => shortcode_exists( 'peptide_news' ),
'coming_soon' => false,
),
array(
'title' => __( 'Subject Log', 'peptide-starter' ),
'description' => __( 'Track outcomes over time. Subjective markers, lab results, protocol changes — your own data, kept private.', 'peptide-starter' ),
@@ -109,3 +101,4 @@ $modules = array(
</div>
</div>
</section>