Compare commits

...

9 Commits

Author SHA1 Message Date
peptiderepo
9d4ce64c1c test(cpt): update rest_namespace assertion — now absent, defaults to wp/v2 2026-04-25 01:55:41 +07:00
peptiderepo
c24259f1d7 fix(cpt): bump version to 0.3.2, update CHANGELOG 2026-04-25 01:53:31 +07:00
peptiderepo
6ab828087d fix(cpt): bump version to 0.3.2, update CHANGELOG 2026-04-25 01:53:27 +07:00
peptiderepo
79259bf5ac fix(cpt): remove rest_namespace so Gutenberg block editor can load peptide posts (v0.3.2) 2026-04-25 01:52:43 +07:00
peptiderepo
deadccc27b ci: split deploy.yml into staging→production pipeline with approval gate 2026-04-24 13:11:24 +07:00
peptiderepo
05cb3caf0d hotfix: CHANGELOG v0.3.1 2026-04-24 12:07:42 +07:00
peptiderepo
21d05427d8 hotfix: bump to v0.3.1 2026-04-24 12:07:40 +07:00
peptiderepo
9c59fe6706 hotfix: add missing PR_Core_Related_Posts_Provider interface (site down — v0.3.1) 2026-04-24 12:07:23 +07:00
peptiderepo
e0fcace223 feat: add Related Articles Section feature (v0.3.0)
Merges feature/related-articles-section.

Adds peptide_topic taxonomy, provider interface, internal posts provider, card grid renderer, admin settings, CSS, and unit tests.

Reviewed: qa-reviews/peptide-repo-core/2026-04-24-a10610ded7df.md
CI: PHP 8.1/8.2/8.3 lint, PHPCS, 300-line check — all green.
2026-04-24 10:25:17 +07:00
6 changed files with 86 additions and 33 deletions

View File

@@ -7,67 +7,84 @@ on:
permissions: permissions:
contents: read contents: read
# Cancel previous deploys if a new push arrives while one is running.
concurrency: concurrency:
group: deploy-peptide-repo-core group: deploy-peptide-repo-core
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
# ── Run CI first ─────────────────────────────────────────────────
ci: ci:
name: Pre-deploy CI name: Pre-deploy CI
uses: ./.github/workflows/ci.yml uses: ./.github/workflows/ci.yml
# ── Deploy via rsync over SSH ──────────────────────────────────── deploy-staging:
deploy: name: Deploy to Staging
needs: ci needs: ci
name: Deploy to Production
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: staging
steps: steps:
- name: Checkout code - uses: actions/checkout@v4
uses: actions/checkout@v4
- name: Setup SSH key - name: Setup SSH key
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: Deploy via rsync → staging
- name: Deploy via rsync
run: | run: |
rsync -avz --delete \ rsync -avz --delete \
--exclude '.git' \ --exclude '.git' --exclude '.github' \
--exclude '.github' \ --exclude 'tests' --exclude 'phpunit.xml.dist' \
--exclude 'tests' \ --exclude 'composer.json' --exclude 'composer.lock' \
--exclude 'phpunit.xml.dist' \ --exclude 'vendor' --exclude 'phpcs.xml.dist' \
--exclude 'composer.json' \ --exclude 'ARCHITECTURE.md' --exclude 'CONVENTIONS.md' --exclude 'CHANGELOG.md' \
--exclude 'composer.lock' \ -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }}" \
--exclude 'vendor' \ ./ ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:~/domains/peptiderepo.com/public_html/staging/wp-content/plugins/peptide-repo-core/
--exclude 'phpcs.xml.dist' \ - name: Staging health check
--exclude 'ARCHITECTURE.md' \ run: |
--exclude 'CONVENTIONS.md' \ sleep 5
--exclude 'CHANGELOG.md' \ STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://staging.peptiderepo.com/ || echo "000")
echo "Staging responded: HTTP $STATUS"
- name: Cleanup
if: always()
run: rm -f ~/.ssh/deploy_key
deploy-production:
name: Deploy to Production
needs: [ci, deploy-staging]
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: Deploy via rsync → production
run: |
rsync -avz --delete \
--exclude '.git' --exclude '.github' \
--exclude 'tests' --exclude 'phpunit.xml.dist' \
--exclude 'composer.json' --exclude 'composer.lock' \
--exclude 'vendor' --exclude 'phpcs.xml.dist' \
--exclude 'ARCHITECTURE.md' --exclude 'CONVENTIONS.md' --exclude 'CHANGELOG.md' \
-e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }}" \ -e "ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }}" \
./ ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:~/domains/peptiderepo.com/public_html/wp-content/plugins/peptide-repo-core/ ./ ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }}:~/domains/peptiderepo.com/public_html/wp-content/plugins/peptide-repo-core/
- name: Purge LiteSpeed Cache - name: Purge LiteSpeed Cache
run: | run: |
ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} \ ssh -i ~/.ssh/deploy_key -p ${{ secrets.SSH_PORT }} \
${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }} \ ${{ secrets.SSH_USERNAME }}@${{ secrets.SSH_HOST }} \
"cd ~/domains/peptiderepo.com/public_html && find wp-content/litespeed/ -type f -delete 2>/dev/null; touch .lscache_purge 2>/dev/null || true" "cd ~/domains/peptiderepo.com/public_html && find wp-content/litespeed/ -type f -delete 2>/dev/null; touch .lscache_purge 2>/dev/null || true"
- name: Production health check
- name: Health check
run: | run: |
sleep 5 sleep 5
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://peptiderepo.com) STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://peptiderepo.com)
if [ "$STATUS" != "200" ] && [ "$STATUS" != "403" ]; then if [ "$STATUS" != "200" ] && [ "$STATUS" != "403" ]; then
echo "ERROR: Site returned unexpected HTTP $STATUS after deploy" echo "ERROR: Site returned HTTP $STATUS"
exit 1 exit 1
fi fi
echo "Site is responsive (HTTP $STATUS)" echo "Production healthy (HTTP $STATUS)"
- name: Cleanup - name: Cleanup
if: always() if: always()
run: rm -f ~/.ssh/deploy_key run: rm -f ~/.ssh/deploy_key

View File

@@ -3,6 +3,17 @@
All notable changes to Peptide Repo Core are documented here. All notable changes to Peptide Repo Core are documented here.
Format: [Semantic Versioning](https://semver.org/). Format: [Semantic Versioning](https://semver.org/).
## [0.3.2] — 2026-04-25
### Fixed
- Remove `rest_namespace` from peptide CPT registration. Custom namespace prevented Gutenberg block editor from loading peptide posts for editing (404 on wp/v2 REST route). All 89 peptide entries are now editable in the block editor. (#5)
## [0.3.1] — 2026-04-24
### Fixed
- Add missing `PR_Core_Related_Posts_Provider` interface file (`class-pr-core-related-posts-provider.php`).
Omitted from v0.3.0 deploy caused a fatal on every page load. Site was down until this fix.
## [0.3.0] — 2026-04-24 ## [0.3.0] — 2026-04-24
### Added ### Added

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* Contract for any provider that retrieves posts related to a peptide.
*
* What: Interface defining the get_posts() contract for related-post providers.
* Who calls it: PR_Core_Related_Posts_Section accepts any implementation.
* Dependencies: None.
*
* @see includes/core/class-pr-core-internal-posts-provider.php — default implementation.
* @see includes/core/class-pr-core-related-posts-section.php — consumer.
*/
interface PR_Core_Related_Posts_Provider {
/**
* Get posts related to a peptide.
*
* @param int $peptide_id The peptide post ID.
* @param int $limit Maximum number of posts to return.
* @return array<int, \WP_Post> Array of WP_Post objects (may be empty).
*/
public function get_posts( int $peptide_id, int $limit ): array;
}

View File

@@ -184,7 +184,8 @@ class PR_Core_Peptide_CPT {
'show_in_nav_menus' => true, 'show_in_nav_menus' => true,
'show_in_rest' => true, 'show_in_rest' => true,
'rest_base' => 'peptides', 'rest_base' => 'peptides',
'rest_namespace' => 'pr-core/v1', // 'rest_namespace' omitted — custom namespaces break Gutenberg
// (it fetches wp/v2 hardcoded). WordPress defaults to wp/v2. (#5)
'menu_position' => 25, 'menu_position' => 25,
'menu_icon' => 'dashicons-database', 'menu_icon' => 'dashicons-database',
'capability_type' => 'post', 'capability_type' => 'post',

View File

@@ -3,7 +3,7 @@
* Plugin Name: Peptide Repo Core * Plugin Name: Peptide Repo Core
* Plugin URI: https://peptiderepo.com * Plugin URI: https://peptiderepo.com
* Description: Canonical peptide schema — shared data layer for the peptiderepo.com ecosystem. Provides the peptide CPT, dosing rows, legal status cells, AI candidate queue, disclaimer component, and JSON-LD output. * Description: Canonical peptide schema — shared data layer for the peptiderepo.com ecosystem. Provides the peptide CPT, dosing rows, legal status cells, AI candidate queue, disclaimer component, and JSON-LD output.
* Version: 0.3.0 * Version: 0.3.2
* Author: peptiderepo * Author: peptiderepo
* Author URI: https://peptiderepo.com * Author URI: https://peptiderepo.com
* License: GPL-2.0-or-later * License: GPL-2.0-or-later
@@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
/* ── Constants ────────────────────────────────────────────────────────── */ /* ── Constants ────────────────────────────────────────────────────────── */
define( 'PR_CORE_VERSION', '0.3.0' ); define( 'PR_CORE_VERSION', '0.3.2' );
define( 'PR_CORE_PLUGIN_FILE', __FILE__ ); define( 'PR_CORE_PLUGIN_FILE', __FILE__ );
define( 'PR_CORE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'PR_CORE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'PR_CORE_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); define( 'PR_CORE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );

View File

@@ -10,7 +10,7 @@
* - TAX_FAMILY constant is removed. * - TAX_FAMILY constant is removed.
* - register_peptide_post_type() guard: no-op when post_type_exists returns true. * - register_peptide_post_type() guard: no-op when post_type_exists returns true.
* - register_peptide_post_type() payload: args match the harmonized contract * - register_peptide_post_type() payload: args match the harmonized contract
* (thumbnail in supports, rewrite slug peptides, pr-core/v1 REST namespace). * (thumbnail in supports, rewrite slug peptides, rest_namespace absent — Gutenberg requires wp/v2).
* - register_taxonomies() guard: no-op when taxonomy_exists returns true. * - register_taxonomies() guard: no-op when taxonomy_exists returns true.
* - register_taxonomies() only registers peptide_category; pr_peptide_family is gone. * - register_taxonomies() only registers peptide_category; pr_peptide_family is gone.
* *
@@ -65,7 +65,7 @@ pr_assert_equals( true, $args['public'] ?? null, 'public = true' );
pr_assert_equals( true, $args['publicly_queryable'] ?? null, 'publicly_queryable = true' ); pr_assert_equals( true, $args['publicly_queryable'] ?? null, 'publicly_queryable = true' );
pr_assert_equals( true, $args['show_in_rest'] ?? null, 'show_in_rest = true' ); pr_assert_equals( true, $args['show_in_rest'] ?? null, 'show_in_rest = true' );
pr_assert_equals( 'peptides', $args['rest_base'] ?? null, 'rest_base = "peptides"' ); pr_assert_equals( 'peptides', $args['rest_base'] ?? null, 'rest_base = "peptides"' );
pr_assert_equals( 'pr-core/v1', $args['rest_namespace'] ?? null, 'rest_namespace = "pr-core/v1"' ); pr_assert( ! array_key_exists( 'rest_namespace', $args ), 'rest_namespace absent — defaults to wp/v2 (Gutenberg requirement)' );
pr_assert_equals( 'post', $args['capability_type'] ?? null, 'capability_type = "post"' ); pr_assert_equals( 'post', $args['capability_type'] ?? null, 'capability_type = "post"' );
pr_assert_equals( true, $args['map_meta_cap'] ?? null, 'map_meta_cap = true' ); pr_assert_equals( true, $args['map_meta_cap'] ?? null, 'map_meta_cap = true' );
pr_assert_equals( false, $args['hierarchical'] ?? null, 'hierarchical = false' ); pr_assert_equals( false, $args['hierarchical'] ?? null, 'hierarchical = false' );