# Card Oracle Architecture Guide

This guide explains how the Card Oracle plugin is assembled so developers can quickly find the right layer when adding
features or debugging behaviour. It captures the boot sequence, module boundaries, data model, premium gating, and the
major runtime flows that wire the admin experience to the public shortcodes and Gutenberg block.

## 1. Boot & Runtime Overview

```
+-------------------+       +-------------------+       +-------------------+
| card-oracle.php   |  -->  | Card_Oracle       |  -->  | Admin / Public    |
| - Loads Freemius  |       | - Defines consts  |       | modules register  |
| - Registers hooks |       | - Loads loader    |       | hooks via loader  |
+-------------------+       +-------------------+       +-------------------+
            |                               |                         |
            |                               v                         v
            |                       Card_Oracle_Loader        Shortcodes, REST,
            |                       orchestrates actions      asset enqueues
            v
Freemius SDK boots, premium flags available via card_oracle_fs()
```

1. WordPress loads `card-oracle.php`, which safeguards direct access, initialises the Freemius SDK, and registers the
   activation/deactivation callbacks.
2. `run_card_oracle()` instantiates `Card_Oracle` (defined in `includes/class-card-oracle.php`).
3. The constructor defines global constants, loads dependencies, sets up translation, and delegates hook registration to
   `Card_Oracle_Loader`. Loader centralises all `add_action`/`add_filter` calls so every module can be discovered in a
   single place.
4. Admin and public classes wire their hooks when `define_admin_hooks()` / `define_public_hooks()` run. Because the
   loader owns the actual WordPress registrations, unit tests can instantiate modules without touching global state.

## 2. Module Boundaries

| Layer | Location | Responsibility |
| --- | --- | --- |
| Bootstrap | `card-oracle.php` | Initialise Freemius, register activation/deactivation callbacks, start the plugin. |
| Core | `includes/` | Cross-cutting helpers: loader, i18n, constants, CPT registration handlers, utility functions, shared premium logic. |
| Admin | `admin/` | WP-Admin screens, CPT meta boxes, dashboard widgets, REST endpoints for the block, block assets, wizards, notices, demo data importer.
| Public | `public/` | Shortcodes (`card-oracle`, `card-oracle-daily`, `card-oracle-random`), layout rendering, shuffle/email flows, asset enqueueing. |
| Premium-only | `__premium_only` directories | Gated integrations (e.g. ActiveCampaign, Stripe checkout pieces, premium layouts). Guarded with `card_oracle_fs()->can_use_premium_code__premium_only()`. |
| Tests | `tests/phpunit/` | Mirrors plugin structure to let Brain Monkey mock WordPress APIs while reusing the same autoloaders. |

Admin and public modules stay independent by passing only primitive arguments through the loader. Shared helpers live in
`includes/card-oracle-core-functions.php` so both sides can call into central utilities without circular dependencies.

## 3. Data Model & Storage

Card Oracle stores readings using custom post types and post meta:

- **Custom Post Types**: `co_readings`, `co_positions`, `co_cards`, `co_descriptions`, and premium order/posts.
- **Meta Keys**: Defined in `Card_Oracle::define_constants()` (e.g. `CO_PRESENTATION_LAYOUT`, `CO_REVERSE_PERCENT`). All
  meta writes funnel through admin form handlers, which sanitise and persist values inside dedicated update helpers.
- **Transients / Cache**: `CARD_ORACLE_DAILY_CARD` and `CARD_ORACLE_RANDOM_CARD` cache rendered markup to avoid expensive
  recomputes. Recent optimisations introduced object-cache backed lookup for `get_reading_id_by_name()` to reduce DB load.
- **Settings**: User-configurable options stored under the `card_oracle_settings` option. Helper wrappers (`card_oracle_get_setting`, etc.) ensure defaults and sanitisation are applied consistently.

## 4. User Interface Flows

### 4.1 Administrator Experience

1. **Dashboard & CPT screens**: Admin hooks register menu pages, meta boxes, and list-table columns during `init` /
   `admin_menu`. All reusable UI widgets (wizards, notices) live under `admin/includes/`.
2. **Gutenberg block**: `CardOracleAdmin::register_blocks()` declares the `card-oracle/shortcode-block` server-rendered
   block. During editor load, `enqueue_block_editor_assets()` ships the React component plus styles, while
   `register_rest_routes()` exposes `/card-oracle/v1/readings` so the block can fetch reading metadata.
3. **REST pipeline**: The REST response now includes the deck layout, presentation choices, question layout, and boolean
   toggles. Editors can override each dimension in the sidebar; the block renders a live preview by calling the same
   server-side shortcode render that powers the front end.

### 4.2 Public Rendering

1. **Shortcodes**: `Card_Oracle_Public` registers `card-oracle`, `card-oracle-daily`, and `card-oracle-random`. All
   entries accept either a numeric ID or the new slug-based `name` attribute that resolves through
   `resolve_reading_id_from_atts()`.
2. **Layouts**: `card_oracle_layout_html()` generates the selectable deck presentation (standard, circular, spread, etc.)
   based on per-reading meta. Premium layouts are toggled via Freemius checks.
3. **Reading submission**: The standard and extended layout templates in `public/layouts/` and the shared
   `public/includes/card-oracle-selection-standard.php` drive front-end interaction. They respect shortcode/block
   overrides (deck layout, question placement, table display) using the new override scope helpers.
4. **Emails & Cron**: Premium installs schedule daily cron via `card_oracle_cron_jobs__premium_only()`, reuse the same
   layout rendering, and ship emails through the configured provider (Mailchimp, ActiveCampaign, etc.).

### 4.3 Gutenberg Block → Shortcode Bridge

```
Editor sidebar changes → REST /card-oracle/v1/readings → Block attributes →
render_shortcode_block() → [card-oracle ... overrides] → display_card_oracle_set()
```

- The block serialises overrides (deck layout, presentation, question settings).
- Server render uses the same overrides to prime the selection template so editors see the actual layout from within the
  editor canvas.
- Shortcode output remains backwards compatible for legacy usage—if overrides are omitted the reading’s stored meta wins.

-### 4.4 Demo Data Importer

- JSON bundles live under `assets/readings/`. Each file contains a top-level `reading`, `positions`, `cards`, and optional HTML `description`.
- `CardOracleAdmin::display_card_oracle_demodata_page()` scans the directory at runtime and renders an installer tile per file. The layout mirrors the dashboard “Content Overview” cards.
- Filenames ending in `__premium_only.json` render with a Premium badge but still import through the same code path once the site has a licence.
- The importer skips bundles whose reading title already exists, preventing duplicate seed data. Descriptions are stored on the generated `co_readings` post using `wp_kses_post` to allow curated markup.
- Image imports still route through `CardOracleDemoData::insert_images()`, but uploaded attachments now use the `Marseille - {Card Name}` title convention so they are easy to spot in the media library.

## 5. Premium Gating Strategy

- **Freemius**: `card_oracle_fs()` exposes licence state. Modules call `card_oracle_fs()->can_use_premium_code__premium_only()` before enqueuing premium assets or rendering gated components.
- **File Suffixes**: Premium-only PHP is stored under paths ending with `__premium_only`. Freemius filters prevent those
  files from loading unless a licence is active.
- **Feature Flags**: Public helpers check Freemius before enabling cron-based emails, payment forms, and integration
  dropdowns. Admin UI labels call out premium features so non-licensed installs fall back gracefully.

## 6. Assets, Builds, and Testing

- **Gulp Pipeline**: `gulpfile.js` defines `compile_js` / `compile_css` tasks that rebuild both the human-readable sources
  and their minified counterparts (`*/min`). Contributors should run `npm ci` once and execute `npx gulp compile_js`
  / `npx gulp compile_css` after modifying JS or CSS.
- **PHP Tooling**: Composer provides PHPUnit, PHPCS, and supporting libraries. The canonical test suite is
  `vendor/bin/phpunit --testsuite "Card Oracle Test Suit"` which boots Brain Monkey to fake WordPress functions.
- **Front-end Tests**: Playwright smoke tests (`npm run test:e2e`) exercise Gutenberg insertion, shortcode rendering, and
  shuffle behaviour. Reports land under `playwright-report/`.

## 7. Key Extension Points

- **Filters**: `card_oracle_layout_profiling_enabled`, `card_oracle_layout_html` filters (see `includes/card-oracle-core-functions.php`) allow function wrapping without forking core logic.
- **REST**: Additional fields can piggy-back on `/card-oracle/v1/readings` since the response is standard JSON. Editors
  should keep payloads small to avoid slowing down the block.
- **Shortcodes**: Use slug-based names for new readings. When adding shortcode attributes, update both
  `display_card_oracle_set()` and the block render path so Gutenberg remains in sync.

## 8. Further Reading

- `AGENTS.md` – onboarding guide and repo conventions.
- `README.txt` → *Developer Notes* – commands, layout profiling, shortcode examples.
- `CLAUDE.md` – quick reference maintained for AI coding agents (mirrors key facts for this workspace).
- `docs/perf/` – profiling workflows for layout performance investigations.
- `docs/snippets/shortcodes.md` – shortcode recipe library shared across docs.

Use this architecture guide with `docs/backlog/github-issues-plan.md` when filing structural changes so new tickets have
consistent language about module boundaries and extension hooks.
