ATmosphere PHP Conventions
SkillFiles & storagePHP coding standards and WordPress patterns for ATmosphere plugin. Use when writing PHP code, creating classes, implementing WordPress hooks, or structuring plugin files.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the ATmosphere PHP Conventions skill
What this skill tells your AI
The instructions your AI receives, as published by automattic/wordpress-atmosphere in .agents/skills/code-style/SKILL.md and read by ahel’s review.
Quick-reference for everyday work. Full reference: docs/php-coding-standards.md and docs/php-class-structure.md.
Non-Negotiables
- Text domain: always
'atmosphere'. - Tabs for indentation, spaces inside parentheses,
array()(not[]). - Backslash-prefix WordPress and PHP global functions in namespaced code:
\get_option(),\add_action(),\apply_filters(),\strlen(). - Use imports for cross-namespace references — never inline
\Atmosphere\OAuth\Client. - Yoda conditions for value-vs-variable comparisons:
if ( 'value' === $variable ). unreleasedfor@since/@deprecatedtags on new code — the release script rewrites them.- Never call
\error_log()directly — route every log line throughAtmosphere\debug_log(), which gates onWP_DEBUG, adds the[atmosphere]prefix, and collapses CRLF. Pass the message without the prefix.
File and Class Layout
includes/
├── class-*.php # Atmosphere, API, Publisher, Backfill, Handle, Post_Types, Reaction_Sync, Autoloader.
├── functions.php
├── content-parser/ # Registry, base, and parsers for site.standard.document content.
├── oauth/ # Client, DPoP, Encryption, Nonce_Storage, Resolver.
├── transformer/ # Post, Document, Publication, Comment, Facet, TID (extend Base).
└── wp-admin/ # Admin UI.
integrations/ # Plugin-specific content-parser integrations.
After adding or renaming a class file under the Atmosphere namespace, no Composer autoload step is needed; runtime loading uses includes/class-autoloader.php.
Transformer Pattern
namespace Atmosphere\Transformer;
class Custom extends Base {
public function transform(): array {
// Build the AT Protocol record array.
}
public function get_collection(): string {
return 'app.custom.collection';
}
public function get_rkey(): string {
// Reserve or return the TID; persist to META_TID so it survives retries.
}
}
Always reserve the rkey via meta in get_rkey() — that meta key is the marker Publisher::update_post() uses to distinguish "never published" from "publish attempt failed mid-flight."
Hook Quick-Reference
Transform filters: atmosphere_transform_bsky_post, atmosphere_transform_comment, atmosphere_transform_document, atmosphere_transform_publication, atmosphere_transform_threadgate.
Records: atmosphere_record_tags (tag/keyword list for both record types; runs before the 8-tag cap, so prefer it over the record-level transform filters for tag changes).
Content / composition: atmosphere_content_parser (deprecated; use Content_Parser\Registry::register()), atmosphere_document_content, atmosphere_long_form_composition, atmosphere_teaser_thread_posts.
Gating: atmosphere_syncable_post_types, atmosphere_should_publish_comment, atmosphere_should_publish_bluesky_post (return false for document-only publishing — no app.bsky.feed.post companion; pure filter, forward-only), atmosphere_should_sync_reply, atmosphere_backfill_query_chunk_size, atmosphere_oauth_redirect_uri, atmosphere_client_metadata, atmosphere_publish_retry_delays (backoff ladder for failed publish/update cron workers; length = retry budget; empty array disables retries).
Actions: atmosphere_publishing, atmosphere_publish_post_result, atmosphere_publish_comment_result, atmosphere_update_skipped_unsynced_post, atmosphere_long_form_strategy_downgraded, atmosphere_reaction_synced. atmosphere_publishing receives the current WP_Post and is not a request-wide guard.
Logging: atmosphere_debug_log — (bool $enabled, string $message); defaults to the WP_DEBUG state, lets operators opt log lines in/out independently of WP_DEBUG.
Test-only: atmosphere_pre_apply_writes / atmosphere_pre_get_record — Publisher fixture uses these to short-circuit apply_writes / get_record before the HTTP layer.
Full signatures and docblocks: docs/php-coding-standards.md → Hook Patterns.
Post Visibility and Federation Cleanup
Federation output is remote, site-wide state. Treat a post as publishable only when it is publish, its post type is supported, and post_password is empty. Do not use post_password_required() for AT Protocol records: it depends on the current visitor's unlock cookie and can leak protected content into PDS records.
When a previously-published post leaves public visibility (draft, pending, private, trash, custom non-public status, password applied, or post type support removed), delete remote records rather than updating them with redacted content. Status transitions may queue the normal delete event, but stale publish/update cron handlers must re-check visibility at fire time and call Publisher::delete_post( $post ) directly when local record metadata exists.
Cron Lifecycle — Three-Way Symmetry
Every plugin-owned wp_schedule_* hook MUST appear in Atmosphere\get_cron_hooks() (includes/functions.php). That list drives:
Atmosphere\deactivate()(atmosphere.php)Atmosphere\OAuth\Client::disconnect()uninstall.php
When adding a new cron hook:
- Add it to
get_cron_hooks()— never duplicate the literal in deactivate / disconnect / uninstall. - If the handler issues PDS writes without re-checking
is_connected(), the symmetry is load-bearing: a queued event from a previous connection would otherwise fire against a different repo on reconnect. - If the handler stores or sweeps post/comment meta keys, mirror those keys in
uninstall.php.
This pattern was extracted in PR #32 (review by @kraftbj). Full rationale: docs/php-coding-standards.md → Cron-Specific Rules.
Cron Handler Errors — Never Swallow WP_Error
Cron handlers in register_async_hooks() MUST surface Publisher::* errors via debug_log() — typically through log_cron_error(). wp_schedule_single_event does not retry, so a silent drop loses the only signal operators have for transient PDS failures, expired refresh tokens, or DPoP nonce drift. debug_log() gates the line behind WP_DEBUG (operators can opt in on production via the atmosphere_debug_log filter).
When the handler operates on records the caller has already lost local state for (e.g. atmosphere_delete_comment_record after the WP comment row is gone), include the TID/identifier in the log line so the orphan is recoverable manually.
Inflight-State Races
When a cron handler writes meta both before an apply_writes call (e.g. Comment::get_rkey() persists META_TID) and after (e.g. store_comment_result() writes META_URI), and a concurrent state change can short-circuit the cleanup gates that key off the post-call meta, the handler MUST re-check eligibility after the call returns and roll back if needed.
Concrete pattern: atmosphere_publish_comment → reconcile_comment_after_publish(). Re-fetch the WP object, re-run the eligibility gate, schedule the orphan-cleanup cron (not direct delete) so transient PDS failures retry through the standard channel.
When to Read the Full Docs
- Naming conventions (classes / methods / files / hooks) —
docs/php-coding-standards.md → Naming Conventions. - Security (escaping, sanitization, nonces, capability checks) —
docs/php-coding-standards.md → Security Practices. - Error handling (returning, checking, aggregating
WP_Error) —docs/php-coding-standards.md → Error Handling. - Performance (caching, capped collections, batching) —
docs/php-coding-standards.md → Performance Considerations. - Documentation standards (DocBlock format,
@sincepolicy, inline comment style) —docs/php-coding-standards.md → Documentation Standards. - Class structure (where new classes go, namespace hierarchy, integration patterns) —
docs/php-class-structure.md.
Signals
- GitHub stars
- 52
- Forks
- 3
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
code-style-automattic- Source
- github.com/automattic/wordpress-atmosphere