Compare commits

..

4 Commits

Author SHA1 Message Date
jason e7fc7072fe Sync: confirm this copy is on the GracePress Gitea (2026-10-02 15:59) 2026-10-02 15:59:27 -05:00
jason 331edafb60 Update public WordPress workspace working changes
- GRACEPRESS-PUBLIC-PLUGIN-STANDARDS.md
- PUBLIC-PLUGIN-WORKFLOW.md
- WORDPRESS-ORG-REQUIREMENTS.md
- WORDPRESS-PUBLIC-CONVENTIONS.md
- design/dark-mode-admin-v1.1.0-spec.md
- plugins/bible-reference-tooltips/CHANGELOG.md
- plugins/bible-reference-tooltips/bible-reference-tooltips.php
- plugins/bible-reference-tooltips/readme.txt
2026-10-02 15:57:02 -05:00
jason f2bc638509 Fortify Bible reference interpretation 2026-07-11 20:16:27 -05:00
jason 5835e5a3b2 Add public WordPress release conventions 2026-07-11 19:53:52 -05:00
21 changed files with 3078 additions and 1227 deletions
+2
View File
@@ -7,3 +7,5 @@ vendor/
*.zip *.zip
*.log *.log
.env .env
tools/testlab/generated/
+64
View File
@@ -0,0 +1,64 @@
# GracePress Public Plugin Standards
House requirements for plugins built in the first-class wpplugin lane and intended for eventual WordPress.org publication. These supplement, but never replace, WORDPRESS-ORG-REQUIREMENTS.md.
## A. Product identity and lane separation
- [ ] GP-01: The public plugin is a genuinely independent codebase under wordpress-plugins/<slug>/, not an alias or release flag applied to the internal plugin.
- [ ] GP-02: The public slug, package name, text domain, PHP identifiers, assets, changelog, and metadata are internally consistent.
- [ ] GP-03: The existing internal GracePress plugin remains intact until an explicit migration and retirement decision.
- [ ] GP-04: The public and internal plugins can be installed together during transition without fatal collisions.
- [ ] GP-05: Private catalog updaters, private telemetry, internal control-plane assumptions, private credentials, and infrastructure-specific endpoints are absent from the public plugin.
- [ ] GP-06: Shared option compatibility is intentional and documented; migrations are reversible and never silently destroy settings.
## B. GracePress visual language
- [ ] GP-07: The admin page has a module-specific wrapper and does not depend on GracePress Central or another private plugin for styling.
- [ ] GP-08: The page begins with a large, purposeful branded hero using a dark image or self-contained background, a module-color accent, a strong title, a concise purpose statement, and useful version/status chips.
- [ ] GP-09: Information architecture uses clear sections, cards, tabs, previews, or diagnostics rather than one long undifferentiated settings form.
- [ ] GP-10: Primary actions are obvious, destructive actions are visually distinct, defaults are understandable, and advanced controls are progressively disclosed.
- [ ] GP-11: Controls are compact but not cramped, labels explain outcomes, help text is plain, and saved/error states are unmistakable.
- [ ] GP-12: The interface has a polished narrow-screen single-column layout and remains usable with keyboard navigation, browser zoom, and reduced motion.
- [ ] GP-13: The footer displays the plugin name and version, a restrained GracePress/Christ IT attribution, "All glory to Jesus Christ," and a contextually relevant Bible verse.
- [ ] GP-14: Scripture and branding remain sincere and non-coercive; they do not interrupt public front-end output, create unsolicited links, or conflict with WordPress.org rules.
- [ ] GP-15: Admin assets are local, license-compatible, optimized, and do not leak visitor or administrator data through remote font, image, tracking, or analytics requests.
## C. User experience and feature discipline
- [ ] GP-16: First-run behavior is safe, useful, and understandable without documentation.
- [ ] GP-17: Settings use sensible defaults and offer a reliable reset path without deleting unrelated site data.
- [ ] GP-18: Every visible control maps to real behavior, and every documented feature exists in the release candidate.
- [ ] GP-19: Advanced features solve a demonstrated user problem; competitor research informs prioritization but no proprietary code, wording, art, or distinctive trade dress is copied.
- [ ] GP-20: Features are evaluated for user value, maintenance burden, accessibility, performance, theme compatibility, and review risk before implementation.
- [ ] GP-21: The plugin provides concise in-page usage/help information and enough diagnostics to resolve ordinary problems without exposing secrets.
- [ ] GP-22: Defaults do not unexpectedly alter wp-admin, override accessibility preferences, or apply aggressive visual effects without clear consent.
## D. Engineering and release discipline
- [ ] GP-23: Git source is canonical; scripts/build and scripts/ship are the only packaging and catalog paths.
- [ ] GP-24: A meaningful edit receives a timestamped rollback copy in the modules .old directory before modification.
- [ ] GP-25: Plugin header Version, runtime constant, readme Stable tag, CHANGELOG.md, public readme changelog, dist metadata, and package filename stay synchronized.
- [ ] GP-26: CHANGELOG.md records durable engineering detail; readme.txt records concise public-facing changes.
- [ ] GP-27: Source stays readable and ASCII by default; public copy avoids mojibake, pasted smart punctuation, and unexplained generated code.
- [ ] GP-28: No durable password, token, key, private endpoint credential, or recovery material enters source, documentation, packages, logs, Git, or memory.
- [ ] GP-29: Security, privacy, accessibility, performance, internationalization, and compatibility are release gates, not cleanup tasks deferred until after submission.
- [ ] GP-30: The independent Docker WordPress lab is the primary manual acceptance environment before any production installation.
- [ ] GP-31: The exact built ZIP, not merely the source directory, is installed and exercised before release.
- [ ] GP-32: Build is the default completion boundary. Ship, WordPress.org submission, SVN publication, and production replacement each require explicit authorization.
- [ ] GP-33: One coherent release candidate gets one bounded validation pass; failures return to a named repair phase instead of causing recursive or endless checking.
- [ ] GP-34: A release-readiness report records checklist status, unresolved risks, test evidence, exact artifact identity, and the decision to stop, ship, or submit.
## E. Dark Mode specific acceptance additions
- [ ] DM-01: Dark mode never traps users in an unreadable state and always offers a reliable visible restoration path.
- [ ] DM-02: System preference, manual selection, schedule, and default mode have explicit precedence and deterministic behavior.
- [ ] DM-03: Anti-FOUC behavior does not block rendering, inject invalid markup, or create a flash worse than the condition it addresses.
- [ ] DM-04: Images, video, SVG, canvas, embeds, form controls, code blocks, editors, modals, and admin bars are checked for inappropriate inversion or low contrast.
- [ ] DM-05: Exclusion selectors and custom CSS are bounded, sanitized appropriately for their purpose, documented as powerful advanced settings, and cannot inject executable markup.
- [ ] DM-06: Keyboard shortcuts do not conflict with common browser, editor, or assistive-technology commands and can be disabled if needed.
- [ ] DM-07: Motion and transition effects honor prefers-reduced-motion.
- [ ] DM-08: Front-end JavaScript works without jQuery unless jQuery is an intentional declared dependency, and failure of localStorage degrades safely.
- [ ] DM-09: Generated CSS and inline scripts are minimized, safely encoded, and compatible with common Content Security Policy configurations where practical.
- [ ] DM-10: Theme, block editor, classic editor, full-site editing, logged-out, logged-in, mobile, and multisite-relevant behavior are tested according to declared support.
Last revised: 2026-08-22.
+120
View File
@@ -0,0 +1,120 @@
# GracePress WordPress Public Plugin Workflow
This is the reusable path for converting an internal GracePress plugin into an independent public WordPress.org plugin. The current pilot is Dark Mode by GracePress.
## Gate 0: Establish the standards
Inputs:
- WORDPRESS-ORG-REQUIREMENTS.md
- GRACEPRESS-PUBLIC-PLUGIN-STANDARDS.md
- Current official WordPress.org documentation
Actions:
1. Reconcile the requirements against current official sources.
2. Review GracePress visual, theological, ownership, and release conventions.
3. Mark items REQUIRED, REVIEW GATE, QUALITY GATE, or CONDITIONAL.
4. Obtain agreement on any disputed convention before changing plugin code.
Output: approved reusable checklists.
## Phase 1: Baseline the independent plugin
1. Inventory source files, hooks, settings, assets, external requests, data storage, and release metadata.
2. Install the exact current ZIP in the isolated Tailnet-only WordPress lab.
3. Capture the current admin page, front-end behavior, settings model, and known limitations.
4. Create a checklist status report with Pass, Fail, Not Applicable, and Not Yet Tested states.
Output: a factual baseline, not a redesign.
## Phase 2: Competitor and user-value research
1. Select a small comparison set, beginning with Vampire Dark Mode and two or three maintained alternatives.
2. Use current public descriptions, screenshots, documentation, and hands-on installs where licensing and the lab allow.
3. Build a feature matrix covering setup, scheduling, system preference, controls, exclusions, media handling, accessibility, performance, compatibility, and admin UX.
4. Classify ideas as Adopt, Adapt, Reject, or Investigate.
5. Do not copy proprietary code, text, art, or distinctive trade dress.
Output: prioritized feature brief with rationale and estimated complexity.
## Phase 3: Admin UI information architecture
1. Map settings into user goals rather than implementation order.
2. Design the hero, status summary, primary settings, appearance preview, placement controls, advanced exclusions, help, diagnostics, and GracePress footer.
3. Define desktop, narrow-screen, keyboard, focus, reduced-motion, saved-state, and error-state behavior.
4. Review the proposed structure before implementation.
Output: approved UI specification.
## Phase 4: Bounded implementation
1. Identify exact files to change.
2. Create timestamped rollback copies.
3. Implement the approved UI and prioritized low-risk features in coherent versions.
4. Keep public code independent of the internal plugin and preserve intentional migration compatibility.
5. Update versions and both changelogs with each bounded release candidate.
Output: one coherent candidate version.
## Phase 5: Security and known-bug review
1. Perform static review against WP-18 through WP-35 and the Dark Mode additions.
2. Search current vulnerability databases and official issue sources for the plugins dependencies and relevant patterns.
3. Exercise malicious and malformed settings, capability boundaries, nonces, escaping, selector/custom-CSS handling, and front-end persistence.
4. Classify findings by severity and repair High/Critical findings before any release decision.
5. Record residual risks and non-applicable vulnerability classes.
Output: security and bug report plus repaired candidate.
## Phase 6: WordPress.org compliance pass
1. Run Plugin Check against the exact candidate.
2. Run the official readme validator.
3. Run PHP syntax and WordPress Coding Standards checks.
4. Reconcile headers, versions, stable tag, text domain, privacy, assets, package contents, and source availability.
5. Complete every REQUIRED and REVIEW GATE item or document why submission must stop.
Output: WordPress.org readiness report.
## Phase 7: Test-lab acceptance
1. Install the exact built ZIP on a clean lab site.
2. Exercise install, activation, configuration, normal use, upgrade, deactivation, reactivation, and uninstall.
3. Test declared minimum and current WordPress/PHP combinations.
4. Test representative themes, editor modes, mobile widths, keyboard navigation, reduced motion, system preference, schedule boundaries, and media exclusions.
5. Run with WP_DEBUG and SCRIPT_DEBUG and capture bounded evidence once.
Output: signed-off release-candidate evidence.
## Phase 8: Listing and release materials
1. Finalize readme copy, FAQ, privacy statement, screenshots, icons, banners, and support route.
2. Confirm every claim against the tested package.
3. Build once through scripts/build and inspect the exact ZIP and dist metadata.
4. Update the private catalog only when explicitly requested.
Output: submission-ready ZIP and WordPress.org assets.
## Phase 9: Explicit release decision
Separate approvals are required for:
1. Shipping to the GracePress public-package catalog.
2. Submitting the ZIP for WordPress.org review.
3. Responding to reviewer changes and resubmitting.
4. Publishing to WordPress.org SVN after approval.
5. Replacing or retiring the internal Dark Mode plugin on any production site.
Output: an auditable release with no accidental production migration.
## Immediate Dark Mode sequence
1. Review and approve the two checklists.
2. Baseline the installed 1.0.0 candidate and capture its existing admin UI.
3. Research Vampire Dark Mode and selected alternatives.
4. Produce the feature matrix and proposed admin UI structure.
5. Obtain UI/feature approval.
6. Implement one bounded candidate.
7. Run security, compliance, and lab gates.
8. Stop for the explicit ship/submission decision.
+109
View File
@@ -0,0 +1,109 @@
# WordPress.org Public Plugin Requirements
Canonical reusable checklist for every GracePress plugin intended for the WordPress.org Plugin Directory.
Status labels:
- REQUIRED: Directory rule or submission requirement.
- REVIEW GATE: Common reviewer expectation or security requirement that can block approval.
- QUALITY GATE: GracePress release requirement that exceeds the minimum directory rule.
- CONDITIONAL: Required only when the plugin uses the named capability.
## A. Ownership, identity, and licensing
- [ ] WP-01 REQUIRED: The plugin and every distributed dependency and asset use a GPL-compatible license.
- [ ] WP-02 REQUIRED: GracePress has the legal right to distribute all code, fonts, images, icons, and other bundled material.
- [ ] WP-03 REQUIRED: The plugin name and requested slug do not violate trademarks, impersonate another project, or create misleading affiliation.
- [ ] WP-04 REQUIRED: The submitted ZIP is complete, production-ready, installable through Plugins > Add Plugin, and under 10 MB.
- [ ] WP-05 REQUIRED: The stable plugin is fully functional; no trialware, locked local features, or code designed merely to upsell a paid edition.
- [ ] WP-06 REQUIRED: SaaS dependencies, if any, provide real off-site functionality and are clearly documented.
- [ ] WP-07 REQUIRED: Distributed code is human-readable. Minified or compiled assets include or link clearly to their corresponding source and build instructions.
- [ ] WP-08 REQUIRED: The package contains no illegal, dishonest, deceptive, or abusive behavior.
## B. Directory behavior and ecosystem boundaries
- [ ] WP-09 REQUIRED: The plugin does not track users or contact external services without clear disclosure and, where tracking is involved, explicit informed consent.
- [ ] WP-10 REQUIRED: The plugin does not download or execute executable code from third-party systems.
- [ ] WP-11 REQUIRED: Front-end credits, backlinks, badges, or external links are absent unless the site owner explicitly opts in.
- [ ] WP-12 REQUIRED: The plugin does not hijack the Dashboard, conceal normal WordPress behavior, or show persistent or excessive admin notices.
- [ ] WP-13 REQUIRED: The readme and admin UI are descriptive rather than spammy; competitor names are not used as tags.
- [ ] WP-14 REQUIRED: WordPress-bundled libraries are used instead of shipping duplicate copies when the core library is suitable.
- [ ] WP-15 REQUIRED: Releases use incremented versions and avoid rapid, unnecessary commits or release churn.
- [ ] WP-16 REVIEW GATE: Namespaces, function names, classes, constants, option names, script handles, cron hooks, and other global identifiers use a unique prefix of at least four characters or a unique namespace.
- [ ] WP-17 REVIEW GATE: The plugin can coexist with likely alternatives and with its own previous/private edition without fatal declarations or unintended data corruption.
## C. Security and data handling
- [ ] WP-18 REVIEW GATE: Every privileged action checks an appropriate capability with current_user_can().
- [ ] WP-19 REVIEW GATE: Every state-changing form, URL action, AJAX action, and REST mutation uses and verifies a nonce where appropriate. A nonce is never treated as authorization.
- [ ] WP-20 REVIEW GATE: Input is taken only from required fields, wp_unslash() is applied where WordPress slashes request data, and values are sanitized and strictly validated against allowed types or enums.
- [ ] WP-21 REVIEW GATE: Dynamic output is escaped at the final output point with the correct context-specific function such as esc_html(), esc_attr(), esc_url(), wp_kses_post(), or wp_json_encode().
- [ ] WP-22 CONDITIONAL: Database queries use WordPress APIs and $wpdb->prepare() for dynamic SQL; identifiers and ordering values are allow-listed.
- [ ] WP-23 REVIEW GATE: File operations, uploads, redirects, HTTP requests, and paths are bounded, validated, and use WordPress APIs where available.
- [ ] WP-24 REVIEW GATE: No eval(), create_function(), executable backticks, hidden payloads, obfuscation, unsafe deserialization, or suppressed failures that conceal security-relevant errors.
- [ ] WP-25 REVIEW GATE: Settings callbacks sanitize each option, reject invalid values, and cannot be invoked by unauthorized users.
- [ ] WP-26 CONDITIONAL: AJAX and REST endpoints declare permission callbacks, validate arguments, avoid data exposure, and return structured WordPress responses.
- [ ] WP-27 CONDITIONAL: Scheduled jobs, background tasks, and external callbacks cannot be abused to bypass capabilities, replay privileged work, or generate unbounded load.
- [ ] WP-28 QUALITY GATE: Security review covers stored and reflected XSS, CSRF, privilege escalation, SQL injection, SSRF, open redirects, path traversal, arbitrary file access, unsafe upload, object injection, and denial-of-service risks relevant to the plugin.
- [ ] WP-29 QUALITY GATE: No credentials, API tokens, private endpoints, debug dumps, personal data, or development secrets exist in source, history-bearing release files, or the ZIP.
## D. Privacy, external services, and cleanup
- [ ] WP-30 CONDITIONAL: All collected, stored, transmitted, logged, or browser-stored data is documented accurately, including cookies and localStorage.
- [ ] WP-31 CONDITIONAL: External services identify the service, data sent, purpose, timing, terms, and privacy policy; optional services require appropriate consent.
- [ ] WP-32 CONDITIONAL: Plugins handling personal data integrate with WordPress privacy-policy text, exporters, and erasers when applicable.
- [ ] WP-33 REVIEW GATE: Logging avoids personal or secret data, is disabled or appropriately bounded in production, and documents retention where relevant.
- [ ] WP-34 REVIEW GATE: Deactivation is safe and non-destructive. Uninstall behavior is explicit and removes plugin-owned data only when that policy is documented and appropriate.
- [ ] WP-35 QUALITY GATE: A privacy inventory explicitly records "none" when the plugin has no telemetry, cookies, accounts, personal data, or external requests.
## E. WordPress implementation quality
- [ ] WP-36 REVIEW GATE: The main plugin header has accurate Plugin Name, Description, Version, Requires at least, Requires PHP, Author, License, License URI, and Text Domain fields as applicable.
- [ ] WP-37 REVIEW GATE: Code uses WordPress APIs for settings, HTTP, filesystem, scheduling, scripts/styles, URLs, and localization rather than bypassing them without reason.
- [ ] WP-38 REVIEW GATE: Scripts and styles are registered/enqueued only on the required front-end or admin screens; dependencies and versions are accurate.
- [ ] WP-39 REVIEW GATE: User-facing strings are translatable, use the plugin text domain, avoid runtime-generated translation keys, and escape translated output appropriately.
- [ ] WP-40 REVIEW GATE: Activation, deactivation, uninstall, upgrades, multisite behavior, and missing optional dependencies fail safely without fatal errors.
- [ ] WP-41 QUALITY GATE: New and changed PHP follows WordPress Coding Standards and contains useful inline documentation for public APIs, hooks, and non-obvious behavior.
- [ ] WP-42 QUALITY GATE: The admin UI uses semantic markup, keyboard-operable controls, visible focus, associated labels, sufficient contrast, understandable validation, and responsive layout.
- [ ] WP-43 QUALITY GATE: The plugin does not impose avoidable front-end performance costs, layout shifts, blocking remote requests, repeated database work, or unbounded generated CSS/HTML.
## F. Readme, metadata, and directory assets
- [ ] WP-44 REQUIRED: readme.txt follows the WordPress.org format and passes the official readme validator.
- [ ] WP-45 REQUIRED: Contributors contains valid WordPress.org usernames; Tags contains one to five relevant generic terms; the short description is plain text and no more than 150 characters.
- [ ] WP-46 REQUIRED: Requires at least, Requires PHP, Tested up to, Stable tag, License, and License URI are accurate; requirements also appear correctly in the main PHP header.
- [ ] WP-47 REQUIRED: The plugin header Version, runtime version constant, readme Stable tag, tagged release directory, and changelogs all agree exactly.
- [ ] WP-48 REVIEW GATE: Description, installation, FAQ, privacy disclosure, screenshots, changelog, and upgrade notice describe only behavior present in the submitted package.
- [ ] WP-49 REVIEW GATE: The readme is concise, avoids unsupported claims and keyword stuffing, and remains below the practical 10 KB parser limit.
- [ ] WP-50 REQUIRED: Directory assets use exact lowercase names and dimensions: banner-772x250, banner-1544x500, icon-128x128, icon-256x256, and sequential screenshot-N files when screenshots are declared.
- [ ] WP-51 REQUIRED: WordPress.org listing assets are prepared for the SVN repositorys top-level assets directory, not bundled unnecessarily inside the runtime plugin ZIP.
- [ ] WP-52 REVIEW GATE: Asset rights, accessibility, legibility, image size limits, and right-to-left compatibility have been reviewed.
## G. Compatibility, testing, and submission evidence
- [ ] WP-53 QUALITY GATE: PHP syntax checks pass for every distributed PHP file.
- [ ] WP-54 QUALITY GATE: WordPress Coding Standards checks pass or each intentional exception is documented.
- [ ] WP-55 REQUIRED: The official Plugin Check plugin runs against the exact release candidate and all errors are resolved; warnings are resolved or explicitly justified.
- [ ] WP-56 REQUIRED: The official readme validator passes the exact release candidate readme.
- [ ] WP-57 QUALITY GATE: Fresh install, activation, configuration, normal use, deactivation, reactivation, upgrade, and uninstall are exercised in an isolated test site.
- [ ] WP-58 QUALITY GATE: The declared minimum and current supported WordPress/PHP combinations are tested in the maintained compatibility matrix.
- [ ] WP-59 QUALITY GATE: WP_DEBUG and SCRIPT_DEBUG testing produces no plugin-owned warnings, notices, deprecated calls, or unexpected output.
- [ ] WP-60 QUALITY GATE: The final ZIP is inspected for required files, excluded development files, secrets, unsafe permissions, stale versions, and a single correct top-level plugin directory.
- [ ] WP-61 QUALITY GATE: The exact final ZIP installs successfully and the active plugin reports the intended version.
- [ ] WP-62 REQUIRED: Submission occurs only with an actively monitored WordPress.org account and email address, and only after the plugin is complete and ready for public use.
- [ ] WP-63 REQUIRED: WordPress.org SVN is treated as the release target after approval; Git remains the canonical development source.
## Official references
- https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/
- https://developer.wordpress.org/plugins/wordpress-org/common-issues/
- https://developer.wordpress.org/plugins/wordpress-org/planning-submitting-and-maintaining-plugins/
- https://developer.wordpress.org/plugins/wordpress-org/how-your-readme-txt-works/
- https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/
- https://developer.wordpress.org/plugins/wordpress-org/plugin-developer-faq/
- https://developer.wordpress.org/plugins/developer-tools/helper-plugins/
- https://developer.wordpress.org/apis/security/
- https://developer.wordpress.org/plugins/privacy/
- https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/
Last reconciled against official documentation: 2026-08-22.
+95
View File
@@ -0,0 +1,95 @@
# WordPress Public Workspace Conventions
This workspace prepares Christ IT WordPress plugins and themes for public distribution. Git is the development source of truth. WordPress.org SVN, public plugin directories, and installed production copies are release targets, not the working source.
## Scope
- Public plugin source lives under `plugins/<slug>/`.
- Shared local release and smoke-test tooling lives under `tools/`.
- Do not remove or replace the matching internal GracePress package until the public package is accepted, published, installed on the target production site, and verified on the public update path.
## Version Discipline
Every meaningful public plugin change must update, in the same commit:
- the plugin header `Version`;
- any runtime version constant;
- `readme.txt` `Stable tag`;
- the plugin `CHANGELOG.md`;
- the `readme.txt` changelog when the change is public-facing.
Use one version number per bounded pass. Do not keep piling unrelated verification fixes into the same version after the pass has been declared complete.
## Public Workspace Identity
Use these names when talking about the two plugin lanes:
- `GracePress workspace`: the internal GracePress catalog workspace.
- `WordPress public workspace`: this standalone public-distribution workspace.
Public workspace files should stay ASCII unless a file already requires another encoding. Avoid emoji, mojibake, smart quotes, and other pasted glyph artifacts in source, readmes, changelogs, and convention docs.
## Admin Page Style
Public plugin admin pages should keep the useful GracePress house rhythm without depending on GracePress:
- a module-specific wrapper;
- a branded hero/header with an image-backed or solid dark background;
- a left accent border tied to the module color;
- a clear plugin title and first-install description;
- version, status, or mode chips when useful;
- card/panel grids for settings and operations;
- compact controls with clear labels;
- a basic Help, About, Usage, or policy section;
- a footer with plugin version, all glory to Jesus Christ, and a relevant Bible verse when appropriate;
- responsive one-column fallbacks for narrow screens.
## Changelog Discipline
The standalone plugin changelog is the durable engineering record. The WordPress.org readme changelog is the public release summary.
For migrated GracePress plugins:
- keep enough prior internal history to explain origin and compatibility;
- clearly mark the public fork version;
- remove or supersede public claims that no longer match the current package;
- record test-lab evidence in Git when a version is considered a release candidate.
## Bounded Verification
Use this default verification loop unless the user explicitly asks for more:
1. Run PHP syntax checks for touched PHP files.
2. Run the configured Docker smoke matrix once.
3. Save smoke results under `tools/testlab/results/`.
4. Review failures only far enough to identify the next code or test fix.
5. After a passing matrix, commit and stop.
Do not start recursive verification such as verifying the verifier, expanding the matrix, or repeatedly re-running browser checks unless a named release gate fails or the user explicitly asks for a broader pass.
## Test Lab
The local Docker test lab belongs in `tools/testlab/` so scripts, environment definitions, and result JSON stay tied to Git history.
Smoke tests should prove the contract, not exhaustively explore the UI:
- plugin activates;
- supported WordPress/PHP versions boot;
- expected plugin version is active;
- required tables are created;
- supported local Bible texts import;
- representative REST lookups pass;
- a browser-facing test page can be seeded for manual review.
Manual browser review is a separate, explicitly requested step.
## Release Boundary
A "build/test/commit and stop" request means:
- complete the named edits;
- run the bounded verification loop once;
- commit the coherent checkpoint if verification passes;
- report what passed and stop.
Do not publish to WordPress.org, production sites, or public update channels unless the user explicitly asks for that release step.
+299
View File
@@ -0,0 +1,299 @@
# Dark Mode by GracePress 1.1.0 Admin Interface Specification
Date: 2026-08-22
Status: proposed for review
Scope: information architecture and visual behavior only
Source changes: none
## Design objective
Replace the long implementation-oriented settings form with a native, purposeful administration workspace. The interface should explain effective behavior before exposing controls, preserve WordPress familiarity, carry the GracePress visual identity without private dependencies, and remain fully useful without JavaScript-only navigation.
## Visual direction
- Light native settings canvas by default; the plugin's admin dark-mode preference may transform it only when that user selects Dark or Auto resolves to dark.
- Large navy hero with cyan module accent, restrained atmospheric geometry, and no remote assets.
- Expressive serif display heading paired with readable WordPress-compatible body typography.
- White or dark-neutral panels with modest borders and shadows.
- Color communicates state but is always paired with text and control semantics.
- No purple-on-white default, novelty gradients, giant decorative icons, or commercial upsell panels.
- Motion is limited to preview changes and status transitions and is removed under prefers-reduced-motion.
## Page shell
1. WordPress administration chrome remains authoritative.
2. Plugin content has a maximum readable width but uses available space on desktop.
3. Hero spans the content width.
4. Section navigation follows the hero.
5. The active section renders below it.
6. A save/status bar follows the section content.
7. The GracePress footer closes the page.
## Hero
Left side:
- Eyebrow: GracePress visual comfort.
- Title: Dark Mode.
- One-sentence user outcome, not engine terminology.
- Status chips: frontend state, default mode, privacy status, version.
Right side:
- Live status summary.
- Effective visitor mode.
- Current user's admin preference.
- Schedule state.
- Toggle placement.
The hero does not contain promotional links, remote imagery, or false health claims.
## Navigation
Sections:
1. Overview
2. Behavior
3. Toggle
4. Rendering
5. Advanced
6. Help
Requirements:
- Native buttons or links with visible selected state.
- Server-rendered fallback permits direct section URLs.
- Keyboard and screen-reader state is explicit.
- Horizontal overflow is contained at narrow widths.
- Settings search is intentionally omitted at the current scale.
## Overview
### Live preview
- Representative heading, paragraph, link, form/control, media sample, and toggle.
- Light/dark preview control.
- Desktop/mobile width control.
- Preview uses unsaved form state without publishing it.
- Preview never loads the public site in a third-party frame.
- Preview motion honors reduced-motion.
### Quick setup
- Frontend enable switch.
- New-visitor default: Light, Dark, or System.
- Current user's admin preference: Light, Dark, or Auto.
- Short explanation that a visitor's manual choice wins after first visit.
### At a glance
- Current toggle style.
- Device visibility.
- Number of exclusions.
- No invented performance or accessibility scores.
## Behavior
### Deterministic precedence
Display the effective order directly:
1. Temporary preview or explicit URL override.
2. Visitor manual choice.
3. Active schedule.
4. Operating-system preference.
5. Site default.
Any implementation change to this order requires documentation and migration consideration.
### Schedule
- Explicit enable switch.
- Configurable start and end time.
- WordPress site timezone displayed by name.
- Runtime evaluation remains cache-safe.
- Overnight ranges work correctly.
- Manual visitor choice remains authoritative.
### Editing surfaces
- WordPress admin preference is per user.
- Admin, block editor canvas, and classic editor may be controlled separately.
- Disabling admin dark mode removes admin CSS, JavaScript, and toolbar controls.
- Disabling frontend dark mode emits no frontend assets or controls.
## Toggle
### Appearance
- Three initial styles: Classic moon, Compact switch, Text button.
- Every style uses a native button.
- Visible focus, descriptive accessible name, aria-pressed, and state text.
- Restrained accent-color setting with contrast validation.
- No unrestricted icon uploads in 1.1.0.
### Placement
- Floating positions: four corners.
- Horizontal and vertical offsets.
- Desktop and mobile visibility.
- Safe z-index below critical modal and consent surfaces.
- Shortcode remains supported.
- Automatic injection into every menu is removed.
- Deliberate menu-item or block integration is deferred unless approved.
## Rendering
### Page transformation
- Existing brightness and contrast behavior receives strict ranges and plain-language help.
- The UI distinguishes page transformation from media correction.
- Invalid values are rejected, not silently reshaped.
### Media
- Image/media brightness.
- Image/media contrast.
- Image/media grayscale.
- Default preserves original media appearance.
- Image/video replacement is out of scope.
### Motion
- None, Gentle fade, and Soft wipe are the initial choices.
- Duration has a bounded range.
- Pulse is not enabled by default.
- prefers-reduced-motion disables nonessential motion.
## Advanced
### Content exclusions
- Search and choose pages/posts by title.
- Saved selections show title and content type, not raw IDs alone.
- Advanced manual ID fallback may be available for recovery.
### Element exclusions
- One selector per parsed item.
- Each selector is validated independently.
- Invalid selectors show actionable errors.
- Common presets may be offered for logos, maps, charts, and embedded media.
### Custom CSS
- Collapsed by default.
- Explicit warning about maintenance and external URL privacy.
- Reject closing markup and unsafe structures.
- Scope examples to the stable public root class.
- Never imply that arbitrary CSS is risk-free.
## Help
- Shortcode and examples.
- Stable root class.
- Public hooks and filters.
- Preference and schedule explanation.
- Privacy statement: no telemetry, no remote runtime assets, browser-local visitor preference.
- Diagnostics summary with no secrets.
- Data retention and uninstall policy.
- WordPress.org support route once the plugin is published.
## Save and reset behavior
- One explicit Save changes action.
- Visible Clean, Unsaved, Saving, Saved, and Error states.
- Validation errors remain beside their controls and are summarized accessibly.
- Reset section and Reset all are distinct.
- Reset requires confirmation and states exactly what will change.
- Reset never deletes unrelated WordPress or internal-plugin data.
- Failed saves preserve entered values.
## GracePress footer
Required content:
- Relevant Scripture: John 1:5 is retained.
- Plugin name and version.
- All glory to Jesus Christ.
- Restrained Christ IT or GracePress attribution only if it complies with WordPress.org rules.
- No front-end backlink and no promotional call to action.
## Responsive behavior
- Desktop: hero and main sections may use two columns.
- Tablet: hero and content stack while navigation remains horizontally usable.
- Mobile: WordPress sidebar is outside plugin ownership; plugin content becomes one column.
- No fixed viewport heights or internal page scrolling.
- Actions wrap without overlap.
- Labels remain visible; icon-only controls have accessible names.
## Accessibility contract
- Native controls are mandatory.
- Labels are programmatically associated.
- Every state has a textual equivalent.
- Focus remains visible.
- Tab order follows document order.
- Contrast meets WCAG AA for text and essential controls.
- Preview controls expose pressed state.
- Dynamic save messages use polite announcements.
- Validation uses an alert summary and field associations.
- No global shortcut fires while focus is in an editable control.
- Reduced-motion preference is honored.
## Implementation boundary
The proposed implementation should remain server-rendered PHP with scoped CSS and small progressive-enhancement JavaScript. React is not justified for this settings count.
Expected source decomposition:
- Main bootstrap/plugin file.
- Admin controller and settings registration.
- Admin view.
- Admin stylesheet.
- Admin JavaScript.
- Frontend engine stylesheet.
- Frontend engine JavaScript.
- Shared validation/helpers.
- Uninstall policy file when approved.
Exact file names will be selected during Phase 4 after the existing single-file plugin receives rollback copies.
## Acceptance for Phase 3
Before implementation, confirm:
- Section names and order.
- Hero/status content.
- Preference precedence.
- Per-user admin model.
- Preview scope.
- Initial toggle styles.
- Schedule model.
- Exclusion workflow.
- Custom CSS policy.
- Save/reset behavior.
- GracePress footer content.
- Deferred-feature list.
No source implementation begins until this specification is accepted or revised.
## Desktop density contract
The WordPress admin menu is the visual density baseline. A wide monitor must create more useful grouping and whitespace, not larger typography or controls.
- Body and control text: approximately 13px at 100 percent browser zoom.
- Secondary help text: 11-12px with sufficient contrast.
- Section headings: 18-20px; only the page title may exceed this materially.
- Standard input/select/button height: 32-38px.
- Card padding: 16-20px.
- Section gaps: 12-18px.
- Hero padding: 20-28px, with a target rendered height under roughly 220px on desktop.
- Control rows should use practical maximum widths; a select with four short options does not need to span a large card.
- Dense related switches may share a compact row or two-column group.
- Large screens add columns, preview area, and useful status information rather than scaling controls upward.
- Mobile touch targets remain at least 44px where needed without forcing desktop controls to that size.
- The layout must be reviewed at 100 percent zoom beside the native WordPress left menu.
- The current 1.0.0 values of 50px hero padding, 35px card padding, 20px card headings, and 15-18px action controls are explicitly rejected.
+43 -1
View File
@@ -1,12 +1,54 @@
# Bible Reference Tooltips Changelog # Bible Reference Tooltips Changelog
## 1.1.7 - 2026-07-12
- Adds BSB and OEB-US as installable open eBible VPL translation options without installing them automatically.
- Reworks the admin page header into a styled first-install hero with version chips and clearer onboarding copy.
- Adds a Christ-honoring admin footer with a relevant Scripture reference.
- Records public workspace admin-page and ASCII/no-mojibake conventions.
## 1.1.6 - 2026-07-12
- Centralizes Bible book aliases, eBible source-code aliases, lookup candidates, repair maps, and frontend detection aliases into one registry.
- Adds an admin text-integrity panel showing canonical book coverage for installed translations.
- Expands the Docker smoke matrix with direct interpretation-registry checks and additional alias REST lookups.
## 1.1.5 - 2026-07-12
- Adds the public workspace conventions and tracked Docker test lab as the bounded release workflow for this public package.
- Adds a settings migration that removes retired external-link options while preserving active tooltip settings.
- Updates the standalone changelog to match the public 1.1.x transition history.
## 1.1.4 - 2026-07-11
- Removes external Biblia, ref.ly, and Logos link generation from the public plugin.
- Adds optional WordPress admin scanning with separate admin scan selectors.
- Improves matching-rule defaults for capitalization, chapter-only references, and public comments.
- Generalizes the open text policy language.
## 1.1.3 - 2026-07-11
- Fixes browser REST lookups when WordPress provides a query-string REST route.
- Improves local test-page layout and removes native browser title tooltips from test references.
## 1.1.2 - 2026-07-11
- Splits eBible VPL source-code resolution from human Bible reference parsing.
- Adds exact source-code handling for compact eBible book identifiers such as `SOL`, `EZE`, `MAR`, `JOH`, `JAM`, `1JO`, `2JO`, and `3JO`.
- Improves compact reference detection for forms such as `Mark10:45` and `2Jn1:6`.
## 1.1.1 - 2026-07-11
- Adds exact eBible VPL aliases for source abbreviations that were not standard display abbreviations.
- Fixes imports for books such as Mark and Ezekiel when VPL source files use compact book codes.
## 1.1.0 - 2026-07-11 ## 1.1.0 - 2026-07-11
- Forked the former internal Bible tooltip package into a standalone public plugin named Bible Reference Tooltips. - Forked the former internal Bible tooltip package into a standalone public plugin named Bible Reference Tooltips.
- Renamed the plugin slug, text domain, REST namespace, script/style handles, admin action names, option key, and local verse table for the public package. - Renamed the plugin slug, text domain, REST namespace, script/style handles, admin action names, option key, and local verse table for the public package.
- Removed internal menu placement and internal logging dependencies from the public package. - Removed internal menu placement and internal logging dependencies from the public package.
- Added public release metadata, GPL license headers, and WordPress.org-oriented naming. - Added public release metadata, GPL license headers, and WordPress.org-oriented naming.
- Kept local Bible text imports, frontend reference detection, hover/focus cards, translation tabs, Biblia.com links, Logos links, skipped-tag controls, comment scanning, and James alias/backfill repairs. - Kept local Bible text imports, frontend reference detection, hover/focus cards, translation tabs, skipped-tag controls, comment scanning, and James alias/backfill repairs.
## 1.0.14 - 2026-06-25 ## 1.0.14 - 2026-06-25
File diff suppressed because it is too large Load Diff
+46 -6
View File
@@ -4,7 +4,7 @@ Tags: bible, scripture, references, tooltips, verses
Requires at least: 6.0 Requires at least: 6.0
Tested up to: 7.0.1 Tested up to: 7.0.1
Requires PHP: 7.4 Requires PHP: 7.4
Stable tag: 1.1.0 Stable tag: 1.1.7
License: GPLv2 or later License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -12,17 +12,17 @@ Detect Bible references in WordPress content and show local hover cards from ope
== Description == == Description ==
Bible Reference Tooltips scans public WordPress content for Bible references such as John 3:16, Romans 8:28, or James 1:5. Detected references can be underlined, linked, and shown with hover or keyboard-focus tooltip cards. Bible Reference Tooltips scans WordPress content for Bible references such as John 3:16, Romans 8:28, or James 1:5. Detected references can be underlined and shown with hover or keyboard-focus tooltip cards.
Verse text is looked up from local open/public-domain Bible texts installed into your WordPress database. The plugin currently supports World English Bible, King James Version, American Standard Version, and Young's Literal Translation imports from eBible VPL packages. Verse text is looked up from local open/public-domain Bible texts installed into your WordPress database. The plugin currently supports World English Bible, King James Version, American Standard Version, Young's Literal Translation, Berean Standard Bible, and Open English Bible (US) imports from eBible VPL packages.
Features include: Features include:
* Frontend Bible reference detection. * Public-site Bible reference detection.
* Optional WordPress admin Bible reference detection.
* Hover and focus tooltip cards. * Hover and focus tooltip cards.
* Local verse storage with no remote lookup required during page views. * Local verse storage with no remote lookup required during page views.
* Multiple installed translations with quick-switch tabs in the card. * Multiple installed translations with quick-switch tabs in the card.
* Biblia.com links and optional Logos Bible Software links.
* Configurable scan selectors, skipped tags, comment scanning, tooltip theme, and verse range limit. * Configurable scan selectors, skipped tags, comment scanning, tooltip theme, and verse range limit.
== Installation == == Installation ==
@@ -41,7 +41,7 @@ No. Public page views use local verse rows stored in your WordPress database. Th
= Which translations are included? = = Which translations are included? =
The plugin can import WEB, KJV, ASV, and YLT from open eBible VPL packages. Modern commercial translations generally require separate licensing and are not bundled. The plugin can import WEB, KJV, ASV, YLT, BSB, and OEB-US from open eBible VPL packages. Modern commercial translations generally require separate licensing and are not bundled.
= Does it work without a specific theme? = = Does it work without a specific theme? =
@@ -49,6 +49,46 @@ Yes. The plugin is standalone and appears under Settings in the WordPress admin.
== Changelog == == Changelog ==
= 1.1.7 =
* Added BSB and OEB-US as installable open eBible VPL translation options.
* Reworked the admin page header and footer for clearer first-install guidance.
= 1.1.6 =
* Centralized Bible book aliases and eBible source-code interpretation into one registry.
* Added an admin text-integrity panel for installed Bible book coverage.
* Expanded smoke-test coverage for source-code normalization, human reference aliases, and compact references.
= 1.1.5 =
* Added the public workspace conventions and tracked Docker test lab as the bounded release workflow.
* Added a settings migration that removes retired external-link options while preserving current tooltip settings.
* Updated the standalone changelog to match the public 1.1.x transition history.
= 1.1.4 =
* Removed external Biblia, ref.ly, and Logos link generation from the public plugin.
* Added optional WordPress admin scanning with separate admin scan selectors.
* Improved matching-rule defaults for capitalization, chapter-only references, and public comments.
* Generalized the open text policy language.
= 1.1.3 =
* Fixed browser REST lookups when WordPress provides a query-string REST route.
* Improved local test-page layout and removed native browser title tooltips from test references.
= 1.1.2 =
* Split eBible VPL source-code resolution from human Bible reference parsing.
* Added exact source-code handling for compact eBible book identifiers such as SOL, EZE, MAR, JOH, JAM, 1JO, 2JO, and 3JO.
* Improved compact reference detection for forms such as Mark10:45 and 2Jn1:6.
= 1.1.1 =
* Added exact eBible VPL aliases for source abbreviations that were not standard display abbreviations.
* Fixed imports for books such as Mark and Ezekiel when VPL source files use compact book codes.
= 1.1.0 = = 1.1.0 =
* Forked the internal package into a standalone public plugin. * Forked the internal package into a standalone public plugin.
@@ -0,0 +1,148 @@
# Dark Mode by GracePress 1.0.0 Baseline
Date: 2026-08-22
Scope: Phase 1 factual baseline only
Release action: none
## Artifact and environment
- Canonical source: /Workspace/gracepress-workspace/wordpress-plugins/dark-mode-by-gracepress/
- Installed artifact: /Workspace/gracepress-workspace/dist/wp-org/dark-mode-by-gracepress.zip
- Package contents: one top-level dark-mode-by-gracepress directory containing the main PHP file, CHANGELOG.md, and readme.txt.
- Installed status: active, version 1.0.0.
- Declared compatibility: WordPress 6.0+, PHP 7.4+, tested through WordPress 7.0.
- Lab runtime: WordPress 7.1, PHP 8.3.33, Twenty Twenty-Five 1.5.
- Lab URL: http://100.64.0.9:8890/
- Exposure: Desktop Tailnet address only.
## Current architecture
- The runtime is one 38 KB PHP file containing PHP, HTML, CSS, and JavaScript.
- It registers activation and deactivation hooks, init translation loading, wp_head/admin_head output, wp_footer/admin_footer output, an admin settings page, an admin-bar node, a menu-item filter, a shortcode, and plugin action links.
- It stores 19 small WordPress options. Every current option is autoloaded.
- It retains legacy option names beginning gracepress_dark_ and gp_dm_ to preserve migration compatibility.
- It has no database tables, REST routes, AJAX actions, scheduled tasks, filesystem writes, uploads, or direct remote HTTP calls.
- It retains conditional calls to private gp_omnilog() and gp_log() functions when those functions exist.
- It retains private-era routing checks for GracePress_Central_Hub, gracepress-theme, and GracePress-specific front-end selectors.
## Current saved configuration
- Default state: follow operating-system preference.
- Front-end floating control: enabled.
- WordPress admin dark mode: enabled.
- Admin/menu integration: enabled.
- Floating pulse: enabled.
- Keyboard shortcut: enabled.
- Toggle: classic icon, normal size, bottom-right.
- Brightness: 95 percent.
- Contrast: 90 percent.
- Media opacity: 85 percent.
- Transition: fade, 250 ms.
- Page exclusions, element exclusions, and custom CSS: empty.
## Admin interface in light mode
The valid visual baseline was captured only after removing the gracepress-dark-active root class. The earlier black rendering was the plugin actively filtering wp-admin and was not the page's native palette.
Strengths:
- Clear two-column desktop layout.
- Large local-only navy hero with cyan accent.
- Four named settings groups.
- Readable white cards and standard form controls.
- Working filter preview.
- Visible primary save and secondary reset actions.
- Responsive intent is present.
- GracePress footer includes John 1:5 and the plugin version.
Weaknesses:
- The hero is visually large but carries no operational status, current mode, quick action, or navigation.
- Information architecture follows implementation categories rather than user goals.
- All controls are presented at once; advanced selectors and custom CSS receive no progressive disclosure or risk explanation.
- There is no concise summary of active behavior, precedence, schedule, exclusions, or where toggles are currently displayed.
- The preview is small and demonstrates only filter values, not toggle styles, theme content, media correction, system preference, or transitions.
- Some labels use engineering language such as "absolute Anti-FOUC synchronization" and "hardware-accelerated CSS inversion" instead of user outcomes.
- Save feedback is generic and the page has no dirty-state indication.
- Reset Filters resets only three filter values, not all settings, but the distinction is easy to miss.
- The full-page browser capture repeated moving sections while stitching the image. The semantic DOM contained one set of controls, so this is recorded as a capture artifact rather than a plugin duplication defect.
## Front-end baseline
- The stock Twenty Twenty-Five homepage renders normally in light mode.
- A floating moon button appears at bottom-right.
- A Dark Mode item appears in the WordPress toolbar for logged-in users.
- The floating button's accessible name is only the moon glyph; it has no descriptive label or pressed-state semantics.
- The pill-switch variant is emitted as a div rather than a native button.
- Front-end and admin scripts use one localStorage key: gracepressDarkMode.
- No external network dependency is required for ordinary rendering.
## Confirmed implementation concerns
These are baseline findings, not the complete Phase 5 security review.
### High-priority functional issues
1. The "Enable in WordPress Admin" setting is not enforced. admin_head and admin_footer always run the dark-mode engine, and the admin-bar toggle is always registered for logged-in users.
2. admin_init force-enables the admin setting once and records gp_dm_adminbar_toggle_restored, overriding the expected role of an administrator-controlled preference.
3. The settings form always posts to admin.php?page=dark-mode-by-gracepress even when the public plugin registered its page under options-general.php. This needs direct workflow testing and likely correction.
4. Time-based mode calculates the WordPress site hour while PHP renders the page. Full-page caching can preserve a stale hour and produce incorrect schedule behavior.
5. In automatic-time mode, the schedule is evaluated before the stored manual preference, so a visitor's manual choice does not persist across page loads while automatic mode is selected.
### Security and review concerns
1. The nonce value is passed directly from POST to wp_verify_nonce() without wp_unslash() and scalar sanitization.
2. Several numeric POST values use absint() without checking that the fields exist first.
3. Enumerated settings such as default state, icon style, button size, position, and transition speed are sanitized as generic text on save rather than rejected against allow-lists.
4. Transition speed is interpolated into CSS without strict CSS-time validation.
5. Element-exclusion text is concatenated into generated selector CSS. It needs bounded selector validation and graceful handling of invalid selectors.
6. Custom CSS is intentionally emitted into every applicable page. The capability boundary is strong, but allowed syntax, external URL behavior, privacy implications, and closing-tag safety require explicit review.
7. Conditional gp_omnilog()/gp_log() integration contradicts the goal of a fully independent public plugin and complicates the readme's no-telemetry assurance.
8. Large inline CSS and JavaScript blocks are emitted on all front-end pages and broadly across wp-admin rather than being scoped and enqueued where practical.
### Accessibility and compatibility concerns
1. Floating icon buttons need descriptive accessible names and state semantics.
2. The pill-switch variant must be a keyboard-operable native control or implement complete button semantics.
3. Ctrl+Alt+D is captured globally, including while users work in form fields and editors, and may conflict with browser, operating-system, or assistive-technology commands.
4. Motion does not currently honor prefers-reduced-motion.
5. Many admin strings remain untranslated.
6. GracePress-specific media selectors remain in the public plugin and need either generalization, documented compatibility purpose, or removal.
7. The readme describes "accessible toggle controls," which is not yet fully supported by the observed controls.
8. The package declares Tested up to 7.0 while the current lab is WordPress 7.1; compatibility metadata must follow completed testing rather than assumption.
## Initial checklist disposition
Pass or substantially present:
- Independent public slug and renamed dmbg_ PHP identifiers.
- GPL-compatible declared license.
- Correct top-level installable ZIP structure.
- No direct executable-code downloads.
- No direct remote HTTP request in the current source.
- manage_options gate on the settings renderer.
- Nonce generation and basic verification are present.
- Stable tag, main header version, runtime constant, and package version agree at 1.0.0.
- Local WordPress.org icons and banners exist outside the runtime ZIP.
- No embedded credentials or API tokens observed.
- Active installation on the isolated lab succeeds.
Needs repair or proof:
- Private logging and routing remnants.
- Strict input validation and nonce handling.
- Admin-enable behavior.
- Settings form action.
- Accessibility semantics and reduced-motion behavior.
- Complete internationalization.
- Admin/front-end asset scoping and performance.
- Privacy statement reconciliation with conditional private logging.
- Readme claims versus actual accessibility.
- Uninstall/data-retention policy.
- Minimum-version matrix.
- Plugin Check, readme validator, PHPCS, debug, upgrade, deactivation, uninstall, and multisite testing.
- Exact final ZIP inspection after the redesign.
## Baseline conclusion
Version 1.0.0 is a working independent installation candidate and a sound behavioral starting point, but it is not yet a WordPress.org submission candidate. The next approved phase is competitor and user-value research, followed by an admin information-architecture proposal. No source repair or redesign was performed during this baseline.
@@ -0,0 +1,155 @@
# Dark Mode Competitor and Feature Matrix
Date: 2026-08-22
Scope: Phase 2 research only
Target: Dark Mode by GracePress
Source changes: none
## Name clarification
No current WordPress.org plugin listing named "Vampire Dark Mode" was found. The likely intended reference is Dracula Dark Mode. Dracula is retained here only as a historical feature and support-case reference because its WordPress.org listing is closed. ProWritingAid also calls its own application theme "Vampire Mode," but that is not a WordPress plugin.
## Comparison set
### WP Dark Mode by WPPOOL
- Current version observed: 5.3.9.
- Active installations observed: 20,000+.
- Current strengths: mature frontend/admin product, OS preference, persistence, blocks, Elementor, presets, media tuning, device visibility, custom triggers, caching awareness, and broad compatibility work.
- Useful lessons: placement flexibility, accessibility repair history, caching awareness, and compatibility testing.
- Poor fit for GracePress: analytics, social sharing, AI preset generation, extensive commercial upsell surfaces, telemetry SDK complexity, and broad feature sprawl.
- Source: https://wordpress.org/plugins/wp-dark-mode/
### Darkify
- Current version observed: 2.0.1.
- Active installations observed: 600+.
- Current strengths: modern sectioned admin, live site preview, settings search, section/all reset, backup/restore, independent frontend/admin/editor controls, mobile controls, selected menu placement, Gutenberg block, and zero frontend output when disabled.
- Useful lessons: information architecture, preview workflow, independent surfaces, bounded reset, and no-output disabled state.
- Caution: very large breadth, React build complexity, many commercial features, and a much larger maintenance surface than GracePress currently needs.
- Source: https://wordpress.org/plugins/darkify/
### Dark Mode Toggle by ScriptsTown
- Current version observed: 1.7.5.
- Active installations observed: 2,000+.
- Tested through WordPress 7.1.
- Current strengths: lightweight activation, persistent user choice, basic switch sizing/position, simple exclusions, and a small administration surface.
- Useful lesson: the free plugin's value proposition is simplicity.
- Public review cautions: limited customization, image inversion complaints, and mobile visibility complaints.
- Source: https://wordpress.org/plugins/dark-mode-toggle/
### Dark Mode for WP Dashboard by Naiche
- Current version observed: 1.3.4.
- Active installations observed: 3,000+.
- Current strengths: per-user Light/Dark/Auto preference, instant toolbar toggle, block editor and Site Editor support, CSS custom-property design tokens, developer filters, and no settings-page bloat.
- Useful lessons: admin preference belongs to each user, editor behavior should be separable, and a stable public body class gives integrations a clean hook.
- Current support caution: third-party screens and editor canvases still need ongoing compatibility work.
- Source: https://wordpress.org/plugins/dark-mode-for-wp-dashboard/
### Dracula Dark Mode, historical reference
- Current status: closed on WordPress.org.
- Useful historical features: frontend/admin mode, menu and floating controls, custom CSS, and extensive styling options.
- High-value caution cases: dark elements active after admin mode was disabled, preference resetting between pages, visual regression after updates, image degradation, editor/media incompatibility, scrollbar changes, excessive toggle z-index, and collisions with browser dark-mode extensions.
- The admin-disable complaint closely matches a defect already confirmed in Dark Mode by GracePress.
- Source: https://wordpress.org/support/plugin/dracula-dark-mode/
## Feature decision matrix
| Feature | Evidence | Decision | Complexity | GracePress direction |
|---|---|---:|---:|---|
| Reliable visitor preference | All maintained competitors | ADOPT | Low | Define and document deterministic precedence across URL override, manual choice, OS preference, schedule, and default. |
| Per-user admin preference | Dashboard plugin | ADOPT | Moderate | Store Light/Dark/Auto in user meta rather than forcing one site-wide admin state. |
| Enforced frontend/admin enable switches | Darkify and competitor bug reports | ADOPT | Low | Disabled must mean no corresponding CSS, JS, toolbar control, or output. |
| Independent block/classic editor controls | Darkify and Dashboard plugin | ADAPT | Moderate | Separate editor-canvas behavior from general wp-admin after the base admin setting is repaired. |
| Configurable schedule range | Darkify and WP Dark Mode | ADOPT | Moderate | Replace fixed 6 PM to 6 AM with start/end controls and explicit site-timezone behavior. |
| Cache-safe schedule evaluation | WP Dark Mode caching feature | ADAPT | Moderate | Do not control caching plugins. Emit stable configuration and calculate current mode safely at runtime. |
| Live preview | Darkify | ADOPT | Moderate | Preview representative text, links, forms, media, toggle, and transition in desktop/mobile frames. |
| Settings section navigation | Darkify | ADOPT | Low | Use native server-rendered tabs or sidebar sections; React is unnecessary for the current scale. |
| Settings search | Darkify | REJECT FOR NOW | Moderate | Eighteen settings do not justify command-palette complexity. Revisit only if the surface grows materially. |
| Section reset and reset-all | Darkify | ADOPT | Low | Make scope explicit, add confirmation, and preserve unrelated settings. |
| Unsaved-change indication | Modern settings behavior | ADOPT | Low | Show dirty state, save success, and validation errors clearly. |
| Import/export settings | Darkify | INVESTIGATE LATER | Moderate | Useful for agencies, but not required for the first public candidate. |
| Clean uninstall option | Darkify | ADOPT | Low | Add an explicit retention/removal policy and an opt-in cleanup choice. |
| Device visibility controls | WP Dark Mode and Darkify | ADOPT | Low | Desktop/mobile visibility is sufficient; avoid unreliable user-agent dependence. |
| Floating offsets and safe z-index | Competitors and Dracula support | ADOPT | Low | Add horizontal/vertical offsets and use a documented, non-aggressive stacking level. |
| Dozens of switch styles | Commercial competitors | REJECT | High maintenance | Keep three or four accessible, distinctive, fully tested controls. |
| Switch color customization | WP Dark Mode and Darkify | ADAPT | Moderate | Offer a small palette and accessible custom accent, not unrestricted style sprawl. |
| Selected-menu placement | Darkify and Dracula | ADAPT | Moderate | Replace automatic injection into every menu with an explicit block/menu-item placement model. |
| Shortcode placement | Existing GracePress feature | KEEP | Low | Preserve shortcode compatibility and improve accessible output. |
| Gutenberg toggle block | WP Dark Mode and Darkify | INVESTIGATE | Moderate | Valuable public-ecosystem integration, but schedule after the core engine and admin redesign stabilize. |
| Elementor widget | WP Dark Mode and Darkify | REJECT FOR FIRST RELEASE | High | Shortcode and block cover the immediate need without vendor-specific maintenance. |
| Custom CSS selector trigger | WP Dark Mode | INVESTIGATE | Moderate | Potentially useful for existing buttons; requires a safe, understandable binding model. |
| Page/post picker exclusions | Darkify and DarkMySite | ADAPT | Moderate | Replace comma-separated IDs with searchable WordPress content selection plus a validated advanced fallback. |
| Element exclusions | All broad competitors | KEEP AND REPAIR | Moderate | Validate bounded selectors, report invalid entries, and provide common presets. |
| Allow-only mode | DarkMySite/Darkify | INVESTIGATE | Moderate | Useful for difficult sites but can complicate the mental model. |
| Color presets | WP Dark Mode and Darkify | INVESTIGATE | High | Our inversion engine does not naturally map to theme palettes; this implies an engine-level design decision. |
| Manual background/text/link colors | Darkify | INVESTIGATE | High | Do not bolt palette controls onto an inversion engine without a coherent rendering model. |
| Image brightness/contrast/grayscale | Darkify, DarkMySite, WP Dark Mode | ADAPT | Low | Replace the ambiguous media-opacity slider with clearer image/media controls and safe defaults. |
| Image/video replacement | Commercial competitors | REJECT | High | Media-library mapping and responsive source handling are disproportionate for this plugin. |
| Scrollbar styling | Darkify | REJECT BY DEFAULT | Low | It creates browser inconsistency and was a Dracula support complaint; allow custom CSS instead. |
| Reduced-motion support | Accessibility requirement | ADOPT | Low | Disable pulse and veil motion under prefers-reduced-motion unless the user explicitly opts back in. |
| Descriptive ARIA and pressed state | WP Dark Mode repair history | ADOPT | Low | Use native buttons with accessible names, aria-pressed, focus styles, and keyboard operation. |
| Custom keyboard shortcut recorder | Darkify | REJECT | Moderate | Prefer no global shortcut by default; if retained, make it optional and ignore typing/editor contexts. |
| Stable public CSS class and filters | Dashboard plugin | ADOPT | Low | Publish a stable root class and a small documented filter/action contract. |
| Full Site Editor support | Dashboard plugin | ADAPT | Moderate | Add to compatibility testing and repair scope rather than claiming universal support immediately. |
| Third-party plugin compatibility packs | Dashboard plugin | INVESTIGATE LATER | Ongoing | Begin with WordPress core surfaces; add integrations only from reproducible issues. |
| Analytics and usage counters | WP Dark Mode | REJECT | Privacy/maintenance | No tracking or usage analytics belongs in the first public GracePress plugin. |
| AI color generation | WP Dark Mode | REJECT | High/external | Unnecessary external-service, privacy, cost, and review complexity. |
| Social sharing | WP Dark Mode | REJECT | Unrelated | Outside the dark-mode product boundary. |
| Remote fonts/images/CDN assets | Historical competitor cleanups | REJECT | Privacy | Keep every runtime/admin visual asset local and license-compatible. |
| Premium comparison/upsell panels | Commercial competitors | REJECT | Product policy | The public GracePress interface should remain focused and non-spammy. |
## Recommended 1.1.0 product scope
The first redesigned candidate should remain bounded to these changes:
1. Repair and actually enforce frontend and admin enable settings.
2. Move admin preference to per-user Light, Dark, or Auto behavior.
3. Establish and document mode precedence.
4. Replace fixed schedule behavior with configurable start/end times evaluated safely at runtime.
5. Reorganize the admin page into Overview, Behavior, Toggle, Rendering, Advanced, and Help sections.
6. Build a representative live preview with desktop/mobile width controls.
7. Add visible dirty, saving, saved, and validation states.
8. Add section reset and reset-all with explicit scope.
9. Add desktop/mobile toggle visibility and safe floating offsets.
10. Keep a small set of accessible toggle styles and add accent-color control.
11. Replace automatic all-menu injection with a deliberate placement explanation; preserve shortcode support.
12. Repair page/element exclusions and provide clearer validation.
13. Replace media opacity language with image/media brightness, contrast, and grayscale controls where technically coherent.
14. Add reduced-motion behavior.
15. Repair button semantics, labels, state, keyboard behavior, and shortcut conflicts.
16. Emit no frontend assets when frontend mode is disabled and no admin engine when admin mode is disabled.
17. Remove private logging/routing remnants and publish only a small stable integration contract.
18. Add explicit data-retention and clean-uninstall policy.
## Deferred candidates
- Gutenberg block.
- Import/export.
- Custom existing-element triggers.
- Allow-only rendering.
- Color-preset or native recoloring engine.
- Third-party compatibility packs.
- Multisite network policy UI.
## Explicitly rejected direction
- Analytics or telemetry.
- AI-generated themes.
- Social-sharing features.
- Remote runtime assets.
- Large premium-style upsell surfaces.
- Dozens of decorative switches.
- Image/video replacement.
- Vendor-specific page-builder widgets in the first release.
- React solely for the settings page.
- Claims of universal theme/plugin compatibility without a maintained test matrix.
## Phase 2 conclusion
The strongest product opportunity is not matching commercial feature count. It is a smaller, trustworthy dark-mode plugin with deterministic behavior, a much better native admin experience, accessible controls, cache-safe scheduling, no telemetry, and disciplined compatibility claims.
The next phase should convert the recommended 1.1.0 scope into an admin information-architecture specification and visual direction before source edits begin.
+60
View File
@@ -0,0 +1,60 @@
# WordPress Plugin Test Lab
Local Docker test lab for public WordPress plugins.
The lab mounts plugin source from this workspace into disposable WordPress containers, installs WordPress with WP-CLI, activates the selected plugin, and runs smoke tests.
## Environments
Default matrix:
| Environment | WordPress Image | PHP | Database |
| --- | --- | --- | --- |
| `wp7-php84` | `wordpress:php8.4-apache` | 8.4 | MariaDB 11 |
The `wp7-php84` image installs the latest WordPress available in the image line. Legacy WordPress/PHP environments should be added only for an explicit compatibility pass, not as part of the default bounded release gate.
## Commands
Run all configured environments:
```powershell
cd tools\testlab
.\scripts\run-all.ps1
```
Run one environment:
```powershell
cd tools\testlab
.\scripts\run-env.ps1 -Env wp7-php84
```
Stop and remove one environment:
```powershell
cd tools\testlab
.\scripts\down-env.ps1 -Env wp7-php84
```
Stop and remove all environments:
```powershell
cd tools\testlab
.\scripts\down-all.ps1
```
## What The Smoke Test Does
- Starts WordPress and MariaDB.
- Waits for the database and WordPress container.
- Installs WordPress.
- Activates `bible-reference-tooltips`.
- Confirms PHP, WordPress, and plugin versions.
- Confirms the plugin-created table exists.
- Imports WEB, KJV, ASV, and YLT through the plugin's internal importer.
- Confirms imported verse rows and canonical book coverage exist.
- Confirms representative REST lookups, including compact references such as `Mark10:45`, `Eze 36:26`, and `2Jn1:6`.
- Creates a post containing those references.
This stops before WordPress.org SVN or plugin submission work.
+8
View File
@@ -0,0 +1,8 @@
[
{
"name": "wp7-php84",
"wordpressImage": "wordpress:php8.4-apache",
"dbImage": "mariadb:11",
"hostPort": 8070
}
]
+164
View File
@@ -0,0 +1,164 @@
{
"environment": "wp7-php84",
"wordpressImage": "wordpress:php8.4-apache",
"dbImage": "mariadb:11",
"hostPort": 8070,
"startedAt": "2026-07-11T20:12:33.4678261-05:00",
"checks": [
{
"name": "wp-cli available",
"passed": true,
"detail": ""
},
{
"name": "wordpress installed",
"passed": true,
"detail": "installed"
},
{
"name": "runtime versions",
"passed": true,
"detail": "WP=7.0.1 PHP=8.4.23"
},
{
"name": "plugin activated",
"passed": true,
"detail": ""
},
{
"name": "plugin version detected",
"passed": true,
"detail": "version=1.1.6"
},
{
"name": "verse table created",
"passed": true,
"detail": "wp_brt_verses"
},
{
"name": "interpretation registry",
"passed": true,
"detail": "ok"
},
{
"name": "WEB import completed",
"passed": true,
"detail": "WEB import complete: 36,601 verses installed."
},
{
"name": "KJV import completed",
"passed": true,
"detail": "KJV import complete: 35,948 verses installed."
},
{
"name": "ASV import completed",
"passed": true,
"detail": "ASV import complete: 31,086 verses installed."
},
{
"name": "YLT import completed",
"passed": true,
"detail": "YLT import complete: 31,102 verses installed."
},
{
"name": "translation rows stored",
"passed": true,
"detail": "[{\"translation\":\"ASV\",\"row_count\":\"31086\"},{\"translation\":\"KJV\",\"row_count\":\"35948\"},{\"translation\":\"WEB\",\"row_count\":\"36601\"},{\"translation\":\"YLT\",\"row_count\":\"31102\"}]"
},
{
"name": "canonical book coverage",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup John 3:16",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup John 3.16",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Jn3:16",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Romans 8:28",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup James 1:5",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Jas 1:5",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Jm 1:5",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mark 10:45",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mark10:45",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mk 10:45",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Mar 10:45",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Ezekiel 36:26",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup Eze 36:26",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup 2 John 1:6",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup 2Jn1:6",
"passed": true,
"detail": "ok"
},
{
"name": "REST lookup 2 Jo 1:6",
"passed": true,
"detail": "ok"
},
{
"name": "test post created",
"passed": true,
"detail": ""
}
],
"phpVersion": "8.4.23",
"wordpressVersion": "7.0.1",
"pluginVersion": "1.1.6",
"completedAt": "2026-07-11T20:16:05.3978156-05:00",
"passed": true
}
+141
View File
@@ -0,0 +1,141 @@
$ErrorActionPreference = 'Stop'
$LabRoot = Split-Path -Parent (Split-Path -Parent $PSCommandPath)
$WorkspaceRoot = Split-Path -Parent (Split-Path -Parent $LabRoot)
$ConfigPath = Join-Path $LabRoot 'config\environments.json'
$GeneratedRoot = Join-Path $LabRoot 'generated'
$ResultsRoot = Join-Path $LabRoot 'results'
$PluginSource = Join-Path $WorkspaceRoot 'plugins\bible-reference-tooltips'
$PluginSlug = 'bible-reference-tooltips'
$ExpectedPluginVersion = '1.1.6'
function Get-TestLabEnvironments {
Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
}
function Get-TestLabEnvironment {
param([Parameter(Mandatory)][string]$Name)
$envs = Get-TestLabEnvironments
$selected = $envs | Where-Object { $_.name -eq $Name } | Select-Object -First 1
if (-not $selected) {
throw "Unknown environment '$Name'. Available: $($envs.name -join ', ')"
}
return $selected
}
function Convert-ToComposePath {
param([Parameter(Mandatory)][string]$Path)
return ($Path -replace '\\', '/')
}
function Ensure-TestLabDirectories {
New-Item -ItemType Directory -Force -Path $GeneratedRoot, $ResultsRoot | Out-Null
}
function Get-EnvDir {
param([Parameter(Mandatory)][string]$Name)
return Join-Path $GeneratedRoot $Name
}
function Get-ResultFile {
param([Parameter(Mandatory)][string]$Name)
return Join-Path $ResultsRoot "$Name.json"
}
function Write-ComposeFile {
param([Parameter(Mandatory)]$Environment)
Ensure-TestLabDirectories
$envDir = Get-EnvDir $Environment.name
New-Item -ItemType Directory -Force -Path $envDir | Out-Null
$pluginPath = Convert-ToComposePath $PluginSource
$compose = @"
services:
db:
image: $($Environment.dbImage)
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_ROOT_PASSWORD: rootpass
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-uroot", "-prootpass"]
interval: 5s
timeout: 5s
retries: 30
volumes:
- db_data:/var/lib/mysql
wordpress:
image: $($Environment.wordpressImage)
depends_on:
db:
condition: service_healthy
ports:
- "$($Environment.hostPort):80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DEBUG: "1"
volumes:
- wp_data:/var/www/html
- ${pluginPath}:/var/www/html/wp-content/plugins/$PluginSlug
volumes:
db_data:
wp_data:
"@
Set-Content -LiteralPath (Join-Path $envDir 'compose.yml') -Value $compose -Encoding UTF8
}
function Invoke-Compose {
param(
[Parameter(Mandatory)]$Environment,
[Parameter(Mandatory)][string[]]$Arguments
)
$envDir = Get-EnvDir $Environment.name
docker compose -p "wptest-$($Environment.name)" -f (Join-Path $envDir 'compose.yml') @Arguments
if ($LASTEXITCODE -ne 0) {
throw "docker compose failed with exit code ${LASTEXITCODE}: $($Arguments -join ' ')"
}
}
function Invoke-WpCli {
param(
[Parameter(Mandatory)]$Environment,
[Parameter(Mandatory)][string]$Command
)
Invoke-Compose $Environment @('exec', '-T', '-u', '33', 'wordpress', 'bash', '-lc', $Command)
}
function Invoke-WpCliRoot {
param(
[Parameter(Mandatory)]$Environment,
[Parameter(Mandatory)][string]$Command
)
Invoke-Compose $Environment @('exec', '-T', 'wordpress', 'bash', '-lc', $Command)
}
function Write-ContainerPhpFile {
param(
[Parameter(Mandatory)]$Environment,
[Parameter(Mandatory)][string]$ContainerPath,
[Parameter(Mandatory)][string]$Content
)
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content))
Invoke-WpCliRoot $Environment "rm -f '$ContainerPath' && printf '%s' '$encoded' | base64 -d > '$ContainerPath' && chown www-data:www-data '$ContainerPath'" | Out-Null
}
function Write-ContainerTextFile {
param(
[Parameter(Mandatory)]$Environment,
[Parameter(Mandatory)][string]$ContainerPath,
[Parameter(Mandatory)][string]$Content,
[string]$Mode = '0644'
)
$encoded = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($Content))
Invoke-WpCliRoot $Environment "rm -f '$ContainerPath' && printf '%s' '$encoded' | base64 -d > '$ContainerPath' && chmod '$Mode' '$ContainerPath' && chown www-data:www-data '$ContainerPath'" | Out-Null
}
+6
View File
@@ -0,0 +1,6 @@
. "$PSScriptRoot\common.ps1"
foreach ($environment in Get-TestLabEnvironments) {
Write-Host "=== Removing $($environment.name) ==="
& "$PSScriptRoot\down-env.ps1" -Env $environment.name
}
+9
View File
@@ -0,0 +1,9 @@
param(
[Parameter(Mandatory)][string]$Env
)
. "$PSScriptRoot\common.ps1"
$environment = Get-TestLabEnvironment -Name $Env
Write-ComposeFile -Environment $environment
Invoke-Compose $environment @('down', '-v', '--remove-orphans') | Out-Host
+9
View File
@@ -0,0 +1,9 @@
. "$PSScriptRoot\common.ps1"
$environments = Get-TestLabEnvironments
foreach ($environment in $environments) {
$name = [string]$environment.name
Write-Host ""
Write-Host "=== Running $name ==="
& "$PSScriptRoot\run-env.ps1" -Env $name
}
+200
View File
@@ -0,0 +1,200 @@
param(
[Parameter(Mandatory)][string]$Env
)
. "$PSScriptRoot\common.ps1"
$environment = Get-TestLabEnvironment -Name $Env
Write-ComposeFile -Environment $environment
$result = [ordered]@{
environment = $environment.name
wordpressImage = $environment.wordpressImage
dbImage = $environment.dbImage
hostPort = $environment.hostPort
startedAt = (Get-Date).ToString('o')
checks = @()
}
function Add-Check {
param([string]$Name, [bool]$Passed, [string]$Detail = '')
$script:result.checks += [ordered]@{ name = $Name; passed = $Passed; detail = $Detail }
if ($Passed) {
Write-Host "[PASS] $Name $Detail"
} else {
Write-Host "[FAIL] $Name $Detail"
throw "Check failed: $Name $Detail"
}
}
try {
Invoke-Compose $environment @('up', '-d') | Out-Host
$wpCli = 'if ! command -v wp >/dev/null 2>&1; then curl -fsSL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -o /usr/local/bin/wp && chmod +x /usr/local/bin/wp; fi'
Invoke-WpCliRoot $environment $wpCli | Out-Host
Add-Check 'wp-cli available' $true
if ($environment.PSObject.Properties.Name -contains 'wordpressVersion' -and $environment.wordpressVersion) {
Invoke-WpCli $environment "wp core download --version=$($environment.wordpressVersion) --force --path=/var/www/html" | Out-Host
Add-Check 'wordpress core version prepared' $true "target=$($environment.wordpressVersion)"
}
$installScript = @'
#!/usr/bin/env bash
set -e
for i in $(seq 1 40); do
if wp core is-installed --path=/var/www/html >/dev/null 2>&1; then
exit 0
fi
if wp core install --path=/var/www/html --url=http://localhost:__HOST_PORT__ --title=PluginTest --admin_user=admin --admin_password=adminpass --admin_email=admin@example.test --skip-email; then
exit 0
fi
sleep 3
done
exit 1
'@ -replace '__HOST_PORT__', [string]$environment.hostPort
Write-ContainerTextFile $environment '/tmp/brt-install.sh' $installScript '0755'
Invoke-WpCli $environment 'bash /tmp/brt-install.sh' | Out-Host
$installed = (Invoke-WpCli $environment 'wp core is-installed --path=/var/www/html && echo installed' | Out-String).Trim()
Add-Check 'wordpress installed' ($installed -eq 'installed') $installed
$phpVersion = (Invoke-WpCliRoot $environment "php -r 'echo PHP_VERSION;'" | Out-String).Trim()
$wpVersion = (Invoke-WpCli $environment 'wp core version --path=/var/www/html' | Out-String).Trim()
Add-Check 'runtime versions' $true "WP=$wpVersion PHP=$phpVersion"
$result.phpVersion = $phpVersion
$result.wordpressVersion = $wpVersion
Invoke-WpCli $environment "wp plugin activate $PluginSlug --path=/var/www/html" | Out-Host
Add-Check 'plugin activated' $true
$pluginVersion = (Invoke-WpCli $environment "wp plugin get $PluginSlug --field=version --path=/var/www/html" | Out-String).Trim()
Add-Check 'plugin version detected' ($pluginVersion -eq $ExpectedPluginVersion) "version=$pluginVersion"
$result.pluginVersion = $pluginVersion
$tablePhp = @'
<?php
global $wpdb;
echo $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $wpdb->prefix . 'brt_verses'));
'@
Write-ContainerPhpFile $environment '/tmp/brt-table.php' $tablePhp
$tableExists = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-table.php --path=/var/www/html' | Out-String).Trim()
Add-Check 'verse table created' ($tableExists -eq 'wp_brt_verses') $tableExists
$interpretationPhp = @'
<?php
$plugin = new Bible_Reference_Tooltips();
$ref = new ReflectionClass($plugin);
$call = function($method, ...$args) use ($plugin, $ref) {
$m = $ref->getMethod($method);
$m->setAccessible(true);
return $m->invokeArgs($plugin, $args);
};
$checks = [];
foreach ([
['parse_reference', 'James 1:5', 'JAS'],
['parse_reference', 'Jas 1:5', 'JAS'],
['parse_reference', 'Jm 1:5', 'JAS'],
['parse_reference', 'Mark10:45', 'MRK'],
['parse_reference', '2Jn1:6', '2JN'],
['parse_reference', 'Eze 36:26', 'EZK'],
] as $case) {
$parsed = $call($case[0], $case[1]);
if (!$parsed || $parsed['book'] !== $case[2]) $checks[] = $case[1] . '=>' . json_encode($parsed);
}
foreach ([
['EZE 36:26 source', 'EZE', 'EZK'],
['MAR 10:45 source', 'MAR', 'MRK'],
['JOH 3:16 source', 'JOH', 'JHN'],
['JAM 1:5 source', 'JAM', 'JAS'],
['1JO 1:9 source', '1JO', '1JN'],
] as $case) {
$book = $call('normalize_source_book', $case[1], 'ebible_vpl');
if (!$book || $book['abbr'] !== $case[2]) $checks[] = $case[0] . '=>' . json_encode($book);
}
$aliases = $call('frontend_book_alias_patterns');
$alias_keys = array_map('strtolower', $aliases);
foreach (['James', 'Jm', 'Eze', 'Mar', '2\\s*Jn'] as $needle) {
if (!in_array(strtolower($needle), $alias_keys, true)) $checks[] = 'missing frontend alias ' . $needle;
}
echo $checks ? json_encode($checks) : 'ok';
'@
Write-ContainerPhpFile $environment '/tmp/brt-interpretation.php' $interpretationPhp
$interpretation = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-interpretation.php --path=/var/www/html' | Out-String).Trim()
Add-Check 'interpretation registry' ($interpretation -eq 'ok') $interpretation
$translationsToImport = @('WEB', 'KJV', 'ASV', 'YLT')
foreach ($translationCode in $translationsToImport) {
$importPhp = @"
<?php
`$plugin = new Bible_Reference_Tooltips();
`$ref = new ReflectionClass(`$plugin);
`$m = `$ref->getMethod('import_vpl_translation');
`$m->setAccessible(true);
`$count = `$m->invoke(`$plugin, '$translationCode', false);
echo `$count;
"@
Write-ContainerPhpFile $environment '/tmp/brt-import.php' $importPhp
$imported = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-import.php --path=/var/www/html' | Out-String).Trim()
$importNumberText = [regex]::Match($imported, '[\d,]+').Value -replace ',', ''
$importNumber = if ($importNumberText) { [int]$importNumberText } else { 0 }
Add-Check "$translationCode import completed" ($importNumber -gt 30000) $imported
}
Invoke-WpCli $environment "wp option patch update brt_settings translation WEB --path=/var/www/html" | Out-Host
$countPhp = @'
<?php
global $wpdb;
$rows = $wpdb->get_results('SELECT translation, COUNT(*) row_count FROM ' . $wpdb->prefix . 'brt_verses GROUP BY translation ORDER BY translation', ARRAY_A);
echo json_encode($rows);
'@
Write-ContainerPhpFile $environment '/tmp/brt-count.php' $countPhp
$rowCount = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-count.php --path=/var/www/html' | Out-String).Trim()
Add-Check 'translation rows stored' ($rowCount -match 'WEB' -and $rowCount -match 'KJV' -and $rowCount -match 'ASV' -and $rowCount -match 'YLT') $rowCount
$bookCoveragePhp = @'
<?php
global $wpdb;
$expected = ['GEN','EXO','LEV','NUM','DEU','JOS','JDG','RUT','1SA','2SA','1KI','2KI','1CH','2CH','EZR','NEH','EST','JOB','PSA','PRO','ECC','SNG','ISA','JER','LAM','EZK','DAN','HOS','JOL','AMO','OBA','JON','MIC','NAM','HAB','ZEP','HAG','ZEC','MAL','MAT','MRK','LUK','JHN','ACT','ROM','1CO','2CO','GAL','EPH','PHP','COL','1TH','2TH','1TI','2TI','TIT','PHM','HEB','JAS','1PE','2PE','1JN','2JN','3JN','JUD','REV'];
$translations = ['WEB','KJV','ASV','YLT'];
$missing = [];
foreach ($translations as $translation) {
$found = $wpdb->get_col($wpdb->prepare('SELECT DISTINCT book FROM ' . $wpdb->prefix . 'brt_verses WHERE translation = %s', $translation));
$diff = array_values(array_diff($expected, $found));
if ($diff) $missing[$translation] = $diff;
}
echo $missing ? json_encode($missing) : 'ok';
'@
Write-ContainerPhpFile $environment '/tmp/brt-book-coverage.php' $bookCoveragePhp
$bookCoverage = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-book-coverage.php --path=/var/www/html' | Out-String).Trim()
Add-Check 'canonical book coverage' ($bookCoverage -eq 'ok') $bookCoverage
$refs = @('John 3:16', 'John 3.16', 'Jn3:16', 'Romans 8:28', 'James 1:5', 'Jas 1:5', 'Jm 1:5', 'Mark 10:45', 'Mark10:45', 'Mk 10:45', 'Mar 10:45', 'Ezekiel 36:26', 'Eze 36:26', '2 John 1:6', '2Jn1:6', '2 Jo 1:6')
foreach ($ref in $refs) {
$lookupPhp = @"
<?php
`$request = new WP_REST_Request('GET', '/bible-reference-tooltips/v1/lookup');
`$request->set_param('ref', '$ref');
`$request->set_param('translation', 'WEB');
`$response = rest_do_request(`$request);
`$data = `$response->get_data();
echo (!empty(`$data['ok']) && !empty(`$data['verses'])) ? 'ok' : json_encode(`$data);
"@
Write-ContainerPhpFile $environment '/tmp/brt-lookup.php' $lookupPhp
$lookupResult = (Invoke-WpCli $environment 'wp eval-file /tmp/brt-lookup.php --path=/var/www/html' | Out-String).Trim()
Add-Check "REST lookup $ref" ($lookupResult -eq 'ok') $lookupResult
}
Invoke-WpCli $environment "wp post create --post_type=post --post_status=publish --post_title='Bible Reference Tooltip Smoke Test' --post_content='John 3:16 Romans 8:28 James 1:5' --path=/var/www/html" | Out-Host
Add-Check 'test post created' $true
$result.completedAt = (Get-Date).ToString('o')
$result.passed = $true
} catch {
$result.completedAt = (Get-Date).ToString('o')
$result.passed = $false
$result.error = $_.Exception.Message
throw
} finally {
Ensure-TestLabDirectories
$result | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath (Get-ResultFile -Name $environment.name) -Encoding UTF8
}
@@ -0,0 +1,9 @@
. "$PSScriptRoot\common.ps1"
$envs = Get-Content -LiteralPath $ConfigPath -Raw | ConvertFrom-Json
foreach ($environment in $envs) {
$name = [string]$environment.name
Write-Host ""
Write-Host "=== Seeding $name ==="
& "$PSScriptRoot\seed-bible-reference-page.ps1" -Env $name
}
@@ -0,0 +1,142 @@
param(
[Parameter(Mandatory)][string]$Env
)
. "$PSScriptRoot\common.ps1"
$environment = Get-TestLabEnvironment -Name $Env
$seedPhp = @'
<?php
$books = [
['Genesis', 'Gen', 'Genesis 1:1', 'Gen 1:1'],
['Exodus', 'Exod', 'Exodus 3:14', 'Exod 3:14'],
['Leviticus', 'Lev', 'Leviticus 19:18', 'Lev 19:18'],
['Numbers', 'Num', 'Numbers 6:24', 'Num 6:24'],
['Deuteronomy', 'Deut', 'Deuteronomy 6:5', 'Deut 6:5'],
['Joshua', 'Josh', 'Joshua 1:9', 'Josh 1:9'],
['Judges', 'Judg', 'Judges 6:12', 'Judg 6:12'],
['Ruth', 'Ruth', 'Ruth 1:16', 'Ruth 1:16'],
['1 Samuel', '1 Sam', '1 Samuel 16:7', '1 Sam 16:7'],
['2 Samuel', '2 Sam', '2 Samuel 7:22', '2 Sam 7:22'],
['1 Kings', '1 Kgs', '1 Kings 8:27', '1 Kgs 8:27'],
['2 Kings', '2 Kgs', '2 Kings 6:17', '2 Kgs 6:17'],
['1 Chronicles', '1 Chr', '1 Chronicles 16:34', '1 Chr 16:34'],
['2 Chronicles', '2 Chr', '2 Chronicles 7:14', '2 Chr 7:14'],
['Ezra', 'Ezra', 'Ezra 7:10', 'Ezra 7:10'],
['Nehemiah', 'Neh', 'Nehemiah 8:10', 'Neh 8:10'],
['Esther', 'Esth', 'Esther 4:14', 'Esth 4:14'],
['Job', 'Job', 'Job 19:25', 'Job 19:25'],
['Psalms', 'Ps', 'Psalms 23:1', 'Ps 23:1'],
['Proverbs', 'Prov', 'Proverbs 3:5', 'Prov 3:5'],
['Ecclesiastes', 'Eccl', 'Ecclesiastes 3:1', 'Eccl 3:1'],
['Song of Solomon', 'Song', 'Song of Solomon 2:4', 'Song 2:4'],
['Isaiah', 'Isa', 'Isaiah 40:31', 'Isa 40:31'],
['Jeremiah', 'Jer', 'Jeremiah 29:11', 'Jer 29:11'],
['Lamentations', 'Lam', 'Lamentations 3:22', 'Lam 3:22'],
['Ezekiel', 'Ezek', 'Ezekiel 36:26', 'Ezek 36:26'],
['Daniel', 'Dan', 'Daniel 3:17', 'Dan 3:17'],
['Hosea', 'Hos', 'Hosea 6:3', 'Hos 6:3'],
['Joel', 'Joel', 'Joel 2:28', 'Joel 2:28'],
['Amos', 'Amos', 'Amos 5:24', 'Amos 5:24'],
['Obadiah', 'Obad', 'Obadiah 1:15', 'Obad 1:15'],
['Jonah', 'Jonah', 'Jonah 2:2', 'Jonah 2:2'],
['Micah', 'Mic', 'Micah 6:8', 'Mic 6:8'],
['Nahum', 'Nah', 'Nahum 1:7', 'Nah 1:7'],
['Habakkuk', 'Hab', 'Habakkuk 2:4', 'Hab 2:4'],
['Zephaniah', 'Zeph', 'Zephaniah 3:17', 'Zeph 3:17'],
['Haggai', 'Hag', 'Haggai 2:9', 'Hag 2:9'],
['Zechariah', 'Zech', 'Zechariah 4:6', 'Zech 4:6'],
['Malachi', 'Mal', 'Malachi 3:10', 'Mal 3:10'],
['Matthew', 'Matt', 'Matthew 5:16', 'Matt 5:16'],
['Mark', 'Mk', 'Mark 10:45', 'Mk 10:45'],
['Luke', 'Lk', 'Luke 2:10', 'Lk 2:10'],
['John', 'Jn', 'John 3:16', 'Jn 3:16'],
['Acts', 'Acts', 'Acts 1:8', 'Acts 1:8'],
['Romans', 'Rom', 'Romans 8:28', 'Rom 8:28'],
['1 Corinthians', '1 Cor', '1 Corinthians 13:4', '1 Cor 13:4'],
['2 Corinthians', '2 Cor', '2 Corinthians 5:17', '2 Cor 5:17'],
['Galatians', 'Gal', 'Galatians 5:22', 'Gal 5:22'],
['Ephesians', 'Eph', 'Ephesians 2:8', 'Eph 2:8'],
['Philippians', 'Phil', 'Philippians 4:13', 'Phil 4:13'],
['Colossians', 'Col', 'Colossians 3:23', 'Col 3:23'],
['1 Thessalonians', '1 Thess', '1 Thessalonians 5:16', '1 Thess 5:16'],
['2 Thessalonians', '2 Thess', '2 Thessalonians 3:3', '2 Thess 3:3'],
['1 Timothy', '1 Tim', '1 Timothy 4:12', '1 Tim 4:12'],
['2 Timothy', '2 Tim', '2 Timothy 1:7', '2 Tim 1:7'],
['Titus', 'Titus', 'Titus 2:11', 'Titus 2:11'],
['Philemon', 'Phlm', 'Philemon 1:6', 'Phlm 1:6'],
['Hebrews', 'Heb', 'Hebrews 11:1', 'Heb 11:1'],
['James', 'Jas', 'James 1:5', 'Jas 1:5'],
['1 Peter', '1 Pet', '1 Peter 5:7', '1 Pet 5:7'],
['2 Peter', '2 Pet', '2 Peter 3:9', '2 Pet 3:9'],
['1 John', '1 Jn', '1 John 1:9', '1 Jn 1:9'],
['2 John', '2 Jn', '2 John 1:6', '2 Jn 1:6'],
['3 John', '3 Jn', '3 John 1:4', '3 Jn 1:4'],
['Jude', 'Jude', 'Jude 1:24', 'Jude 1:24'],
['Revelation', 'Rev', 'Revelation 21:4', 'Rev 21:4'],
];
$translations = [
['WEB', 'World English Bible'],
['KJV', 'King James Version'],
['ASV', 'American Standard Version'],
['YLT', 'Young\'s Literal Translation'],
];
$content = '<div class="brt-test-page">';
$content .= '<style>.brt-test-page{box-sizing:border-box;width:min(1520px,calc(100vw - 120px));max-width:none;margin-left:50%;transform:translateX(-50%);font-family:system-ui,-apple-system,Segoe UI,sans-serif}.brt-test-page *{box-sizing:border-box}.brt-test-page p{max-width:760px;margin:0 0 18px;color:#4b5563}.brt-test-grid{display:grid;grid-template-columns:minmax(120px,.65fr) repeat(4,minmax(130px,1fr));border:1px solid #d7dee8;border-bottom:0;border-right:0;background:#fff}.brt-test-row{display:contents}.brt-test-head,.brt-test-book,.brt-test-cell{border-right:1px solid #d7dee8;border-bottom:1px solid #d7dee8;padding:10px 12px}.brt-test-head{position:sticky;top:0;z-index:1;background:#f3f6fa;font-weight:800}.brt-test-book{font-weight:800;background:#fff}.brt-test-cell{min-width:0}.brt-test-cell .ref-pair{display:flex;flex-direction:column;align-items:flex-start;gap:6px}.brt-test-cell a{display:inline-block;max-width:100%;font-weight:700;line-height:1.25;white-space:normal;overflow-wrap:anywhere}@media(max-width:980px){.brt-test-page{width:min(100%,calc(100vw - 36px))}.brt-test-grid{display:block;border:0}.brt-test-head{display:none}.brt-test-row{display:grid;grid-template-columns:1fr;border:1px solid #d7dee8;margin:0 0 12px}.brt-test-book{background:#f3f6fa}.brt-test-cell{display:grid;grid-template-columns:54px 1fr;gap:10px}.brt-test-cell:before{content:attr(data-version);font-weight:800;color:#4b5563}}}</style>';
$content .= '<p>This page intentionally includes one full-name and one abbreviated Bible reference for every book of the Bible across all installed local translations.</p>';
$content .= '<div class="brt-test-grid">';
$content .= '<div class="brt-test-head">Book</div>';
foreach ($translations as $translation) {
$content .= '<div class="brt-test-head">' . esc_html($translation[0]) . '</div>';
}
foreach ($books as $book) {
[$name, $abbr, $fullRef, $abbrRef] = $book;
$content .= '<div class="brt-test-row">';
$content .= '<div class="brt-test-book">' . esc_html($name) . '</div>';
foreach ($translations as $translation) {
[$code, $label] = $translation;
$content .= '<div class="brt-test-cell" data-version="' . esc_attr($code) . '"><div class="ref-pair">';
$content .= '<span>' . esc_html($fullRef) . '</span>';
$content .= '<span>' . esc_html($abbrRef) . '</span>';
$content .= '</div></div>';
}
$content .= '</div>';
}
$content .= '</div></div>';
$existing = get_page_by_path('bible-reference-tooltip-test');
$post_data = [
'post_title' => 'Bible Reference Tooltip Test',
'post_name' => 'bible-reference-tooltip-test',
'post_type' => 'page',
'post_status' => 'publish',
'post_content' => $content,
];
if ($existing) {
$post_data['ID'] = $existing->ID;
$page_id = wp_update_post($post_data, true);
} else {
$page_id = wp_insert_post($post_data, true);
}
if (is_wp_error($page_id)) {
fwrite(STDERR, $page_id->get_error_message());
exit(1);
}
update_option('show_on_front', 'page');
update_option('page_on_front', (int) $page_id);
$settings = get_option('brt_settings', []);
if (is_array($settings)) {
$settings['translation'] = $settings['translation'] ?? 'WEB';
update_option('brt_settings', $settings, false);
}
echo 'seeded front page ' . $page_id . ' with ' . count($books) . ' books, ' . count($translations) . ' translations, and ' . (count($books) * count($translations) * 2) . ' reference links';
'@
Write-ContainerPhpFile $environment '/tmp/brt-seed-front.php' $seedPhp
Invoke-WpCli $environment 'wp eval-file /tmp/brt-seed-front.php --path=/var/www/html' | Out-Host