# Changelog

All notable changes to BW AI Schema Pro are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.2.2] - 2026-06-01

### Added (Team Survey — inline setup UI)
- When no team post type is mapped yet, the Team Surveys page now
  shows a setup screen inline instead of pushing the admin off to
  the main AI Schema Pro settings page. The screen offers:
  - **Likely matches** — scored suggestions based on common team-CPT
    slugs and labels (`team`, `team_member`, `our-team`, `staff`,
    `people`, `employee`, `crew`, etc.). Each candidate shows the
    label, the slug, and the published post count so admins can
    confirm at a glance.
  - **Fallback dropdown** of all non-core public CPTs on the site,
    alphabetical. Overrides the radio choice if both are selected.
  - **"Connect this post type" button** — writes the chosen slug
    to the existing `bw_schema_team_post_type` option (the same
    one the main AI Schema Pro settings page writes to). Admin can
    still change it from the main settings page later.
- New **"Proceed without a team post type" button** with a warning
  notice explaining the tradeoff: submissions can still be
  collected and reviewed (they all wait in the holding queue),
  but the publish step has no destination until a CPT is
  connected. Sets a sticky `bw_schema_survey_proceed_without_team_cpt`
  option so the workflow unlocks.
- When the admin is in "proceed without" mode, a persistent yellow
  banner appears at the top of the Team Surveys queue with a
  "Connect one now" link that takes them back to the setup screen
  (via a nonced GET). Manual entry (`Add team info manually`)
  shows a friendlier "set up a team post type to use this" notice
  rather than a broken form.

### Added (internal)
- `BW_Schema_Survey::is_setup_complete()` — true when a team CPT is
  mapped OR proceed-without is set. Now used as the gate everywhere
  the workflow needed `BW_Schema_Team_Member::get_team_post_type()`,
  including `is_open()`.
- `BW_Schema_Survey::suggest_team_post_types()` /
  `all_eligible_post_types()` /
  `set_team_post_type()` /
  `set_proceed_without_team_cpt()` /
  `is_proceeding_without_cpt()` — public helpers for the setup UI
  and any future "fix it" actions elsewhere in the admin.

## [2.2.1] - 2026-06-01

### Added (Team Survey — reset controls for testing)
- New "Reset" section at the bottom of the Team Survey settings page
  with two actions, both gated by JS confirm dialogs:
  - **Delete all submissions** — empties the
    `bw_schema_survey_responses` table. Survey window, URL, and
    settings stay as-is. Use between test runs to re-submit without
    starting from scratch.
  - **Reset everything** — closes the window, deletes all
    submissions, rotates the URL token (invalidating the current
    public link immediately), and restores every Team Survey
    setting (slug, intro HTML, notification email + toggle, grant
    Editors toggle) to its default. Caps are re-synced so the
    Editor role's `bw_schema_moderate_team_surveys` cap reflects
    the reset state. Schema data already published onto team
    posts is intentionally not touched — admins can edit those
    posts directly to clear.
- New helper `BW_Schema_Survey_Store::truncate_responses()` for the
  single SQL DELETE used by both reset actions. Returns rows
  deleted so the success notice can show the count.

## [2.2.0] - 2026-05-28

### Fixed (recurring "wrong byline link / 404 author" bug on Kadence sites)
- **Byline link `href` is now correctly overridden server-side** when a
  post has a team-member author override (e.g. risealliance.com's
  /resource/ posts displayed by Robert DiNozzi).
  Previously: `the_author` filter overrode the *display name* only;
  the link `href` still pointed at the WP author archive
  `/author/{post_author}/`. A wp_footer JavaScript rewriter masked
  this by replacing the visible text after page load, so the byline
  *looked* right but the link still went to the wrong place — and
  bots / paste-the-URL users hit 404s. Now three filters work
  together server-side:
  - `get_the_author_display_name` -> overrides byline name
  - `get_the_author_user_url` -> overrides Kadence's preferred href
    source (`get_the_author_meta('url')`). This is the actual filter
    Kadence prefers — note WP renames the `url` field to `user_url`
    internally, so the filter is `get_the_author_user_url`, not
    `get_the_author_url`.
  - `author_link` -> overrides the byline href when themes use
    `get_author_posts_url()` instead (fallback)
  All three are scoped to: singular-post context, same author as the
  queried post. Comments, widgets, user-profile reads elsewhere are
  untouched. The `the_author` filter and the JS rewriter remain in
  place as belt-and-braces.
- **`/author/USERNAME/` redirect has a fallback now.** The
  `_bw_schema_linked_user` meta on the team page (the configuration
  pointer that historically drifts due to UpdraftPlus restores,
  "Rewrite & Republish," CSV reimports, etc.) is no longer the only
  way to resolve the redirect target. When that meta is empty, the
  plugin now looks at recent posts authored by the visited WP user
  for one with a team-member schema author override, and redirects
  to that team page. This rescues the common drift case without
  requiring the user to keep the meta synced manually.
- **Admin notice on the team CPT edit screen** when this team page
  is referenced as a schema author on at least one post but has no
  Linked WordPress User set. Loud yellow banner with copy that
  points at the field to fix. Prevents the bad state from being
  silent — even if the byline link works (via the new
  `author_link` filter), this still flags the config gap so the
  /author/X/ redirect stays solid.

### Added (manual admin intake — Path 1)
- **New admin page: Add team info manually.** Reachable from the
  "Add team info manually" button at the top of the Team Surveys
  queue, and from the empty-queue state. Renders the same survey
  questions in admin context, lets admin pick an existing team member
  or create a new draft, and on save creates a response with
  `status=structured` (skips moderator triage — admin is trusted) with
  the structured payload pre-filled by the deterministic raw→structured
  parser. Lands the admin on the Schema Review tab so they can refine
  before publishing.
- Empty-queue state now surfaces both intake paths (manual entry,
  public survey link) so first-time users know how to start.

### Fixed (rewrite registration — 404 on the survey link)
- **`register_rewrite()` is now called directly from
  `BW_Schema_Survey::init()` instead of being hooked onto `init` at the
  same priority as the caller.** `WP_Hook`'s priority foreach iterates
  an array copy of the bucket, so a same-priority callback added during
  iteration never fires on the current request — meaning the rule was
  missing from `extra_rules_top` on every request, and a follow-up
  `flush_rewrite_rules()` (from a permalink resave, etc.) would wipe
  the activation-time rule out of the DB-stored `rewrite_rules` option.
- **Added one-shot version-tracked auto-flush** via
  `BW_Schema_Survey::maybe_flush_rewrites()`, gated on the
  `bw_schema_survey_rewrite_version` option. Handles file-only deploys
  (no activation cycle) so the rule lands in the DB on the next request
  without needing the user to deactivate/reactivate or visit Settings →
  Permalinks. Subsequent requests are a single option lookup; the
  flush runs at most once per deploy.
- Settings handlers for slug change and token rotation now also
  re-register the rewrite, flush, and bump the version option in one
  shot — so URL rotation invalidates the old URL immediately.

### Added (Team Survey workflow — Phase 1)
- **New module: Team Survey workflow** for collecting team-member info
  in-WordPress instead of via Google Forms. Replaces the out-of-band
  intake process with a four-stage pipeline: public survey → moderator
  triage → schema-shaped review → publish to live Person-schema fields.
  See `docs/SPEC-team-survey.md` for the full spec.
- Public survey page at `/{slug}/{token}/` (default
  `/bw-team-survey/<token>/`), served via a plugin-owned rewrite rule.
  Robots-blocked (`<meta robots noindex>` + `X-Robots-Tag` header) so
  the rotatable token never lands in any index.
- Time-limited open window: admin opens the survey for up to 7 days at
  a time, with a "Renew" action for up to another 7 days. Survey is
  closed by default until explicitly opened.
- New DB table `{prefix}bw_schema_survey_responses` for staging
  responses (raw + structured payloads, status, moderator notes).
  Installed via dbDelta on activation/upgrade; schema version tracked
  in `bw_schema_survey_db_version`.
- New capability `bw_schema_moderate_team_surveys`. Granted to
  administrators by default; settings toggle to also grant to editors.
- Admin pages under the existing AI Schema Pro options menu: queue,
  per-submission detail (triage / schema review / publish tabs),
  settings (open/renew, regenerate URL, notifications, capability
  grant).
- Holding queue for "I'm not listed" submissions — moderator
  graduates them to a team CPT draft before they enter the workflow.
- Anti-abuse: honeypot field, 3-second time-trap, IP rate-limit
  (5/hour, same pattern as `BW_Schema_Security::check_rate_limit()`),
  WP nonce on the form. No CAPTCHA — moderation gating is the primary
  defense.
- Precondition gate: workflow is hidden / closed until a team post
  type is mapped in the existing AI Schema Pro settings.

### Notes
- AI features (mosiah-backed research and parse assists) are
  intentionally **out of scope for 2.2.0** — Phase 2 / 2.3.0+. The
  data model is AI-agnostic so Phase 2 layers on without schema
  changes. See `docs/SPEC-team-survey.md` § Phase 2 for the planned
  contract.
- All new code lives under the grandfathered `bw_schema_*` prefix per
  `CLAUDE.md` § Pre-existing-plugin exception. The 3.0.0 rename will
  migrate the new keys alongside the existing ones.

## [2.1.5] - 2026-05-27

### Fixed (performance)
- **CRITICAL: Removed `BW_Schema_Team_Member::remove_remaining_schemas()`** and its
  `add_action( 'wp_head', ..., 0 )` registration in
  `includes/class-bw-schema-team-member.php`. On team-member pages it ran every
  `wp_head` and iterated the entire `$wp_filter['wp_head']` table using fragile
  `strpos()` string-matching to remove "schema-looking" hook keys. Both expensive
  and dangerous (could match legitimate non-schema hooks). The job it was trying
  to do — disabling other plugins' schema output — is already handled correctly
  by `BW_Schema_Core::disable_conflicting_schema()`, which uses named filter
  hooks driven by the `bw_schema_disable_sources` setting. The
  `remove_all_actions( 'wp_head', 60 )` call in `disable_conflicting_schemas()`
  is retained — that's the Yoast-priority cleanup, not the catch-all iteration.

- **HIGH: Added transient fallback to `BW_Schema_Cache`** in
  `includes/class-bw-schema-cache.php` for hosts without a persistent object
  cache (Flywheel, most shared hosts). Previously `get`/`set`/`delete` used only
  `wp_cache_*`, which defaults to request-scoped on those hosts — meaning the
  cache was a no-op and schema regenerated on every page load. The class now
  routes through transients (DB-backed, persistent across requests) when
  `wp_using_ext_object_cache()` returns false; sites with a real object cache
  continue using `wp_cache_*`. `clear_all()` nukes the plugin's own transients
  via a prefix-scoped DELETE on the options table (no other plugin's data is
  touched). Cache key prefix is `bw_schema_` so all keys are namespaced.

### Changed (performance)
- **Consolidated 6 plugin-owned `init` hooks into one wrapper** in
  `bw-ai-schema-pro.php`. Previously each module (textdomain, Cache, Hooks,
  Security, Author_Override, Team_Member) registered its own
  `add_action( 'init', ... )`; WP walks `$wp_filter['init']` on every request,
  so cutting 6 entries down to 1 (named `init_default_priority_modules`) saves
  per-request work. **The priority-1 (`maybe_migrate_options`) and priority-5
  (`disable_conflicting_schema`) registrations remain separate** — they need
  their explicit priorities.

- **`BW_Schema_Renderer` is now a singleton** (via `get_instance()`). The
  renderer is stateless — `render()` reads context from globals/options/post
  meta each call — but was constructed fresh on every `wp_head` and every
  schema-preview AJAX request. Both call sites in `bw-ai-schema-pro.php` now
  reuse the shared instance.

### Security
- `maybe_redirect_to_setup()`: gate the `?skip_setup=1` self-completion of the setup
  wizard behind `current_user_can( 'manage_options' )`. Previously any logged-in user
  reaching wp-admin (e.g. a subscriber on their own profile page) could trip the
  "setup complete" flag. Impact was minor (only suppressed the wizard redirect for
  admins) but it was the single nonce/cap gap surfaced by the 2026-05-06 security
  audit. See `docs/SESSION-LOG.md` for the audit summary.

## [2.1.4] - 2026-05-06

### Changed
- **Update server moved.** Plugin now checks for updates from
  `https://plugins.bowden.works` (the bw-update-server in the bw-plugins framework)
  instead of the legacy `https://bwgeo.demoing.info/wp-content/uploads/plugin-updates/`.
  No functional change for site administrators — updates continue to arrive automatically;
  only the source server has changed.
- Plugin headers updated to declare `Requires at least: 6.0`, `Requires PHP: 7.4`, and
  `Update URI` per the BW plugins framework standard. Plugin URI and Author URI now
  point at `bowden.works` rather than the legacy `bowdenworks.com`.
- License field standardized to `GPL-2.0-or-later`.

### Added
- `BW_AI_SCHEMA_PRO_VERSION` constant (mirror of `BW_SCHEMA_VERSION`) so BW release
  tooling can introspect the version. Internal code continues to use `BW_SCHEMA_VERSION`.
- ABSPATH guard added to `includes/class-bw-schema-author-box.php`.
- Imported into the bw-plugins development framework. Source moved from
  `/srv/apps/bwgeo/wp-content/plugins/bw-ai-schema-pro/` to
  `/srv/apps/bw-plugins/wp-content/plugins/bw-ai-schema-pro/`. See `docs/ROADMAP.md`
  for the follow-up rename + prefix-conformance plan.

## [2.1.3] - pre-2026-05-06

History prior to bw-plugins import is not captured here. The plugin's prior changelog
(if any) lived in `/srv/apps/bwgeo/`. Future entries follow the Keep a Changelog format.
