Skip to content

fix: disable smilie conversion for imported static content (#780) - #788

Merged
chubes4 merged 2 commits into
Automattic:mainfrom
faisalahammad:fix/780-disable-smilies
Aug 3, 2026
Merged

fix: disable smilie conversion for imported static content (#780)#788
chubes4 merged 2 commits into
Automattic:mainfrom
faisalahammad:fix/780-disable-smilies

Conversation

@faisalahammad

@faisalahammad faisalahammad commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Imported static sites lose literal text because WordPress core maps emoticons like :) to an external emoji image (wp-includes/images/smilies). That shifts heading geometry, breaks offline visual capture, and makes the imported page no longer match the source.

This adds a disable_smilies boolean option (default true) to the import and site-plan materialization contracts. When materialization activates the imported theme and the option is enabled, the plugin sets the core use_smilies option to false, so imported literal :) stays text and no external emoji image is requested. The materialization receipt records the policy under completed.runtime_policy.disable_smilies as a { requested, applied } pair, so the requested policy is explicit and the applied policy is only reported when the runtime mutation actually ran.

Fixes #780

Changes

includes/class-static-site-importer-website-artifact-import-input.php

Declares disable_smilies in SCHEMA_PROPERTIES, defaults it to true in normalize(), and adds it to the bool-cast list. The default is part of the normalized input contract, so CLI, REST, and ability entrypoints that build import input get it through normalize().

includes/abilities.php

Adds disable_smilies (boolean) to the materialize-wordpress-site-plan ability input schema. The import-website-artifact ability inherits it from SCHEMA_PROPERTIES.

includes/class-static-site-importer-wordpress-site-plan-materializer.php

When the activate arg is set and disable_smilies is not explicitly false, sets update_option( 'use_smilies', false ). The call is guarded by the activate block, so a non-activating import never mutates the currently active site's global option. If the write fails, materialization fails closed (disable_smilies_not_applied) instead of claiming the policy was applied.

The receipt records the policy under completed.runtime_policy.disable_smilies:

  • requested is the caller's flag, defaulting to true.
  • applied is only true when the activation-time update_option( 'use_smilies', false ) actually ran.

Why the site option instead of a companion-plugin filter

The previous approach emitted a remove_filter( 'the_content', 'convert_smilies', 20 ) call from the generated companion plugin. That only touches the the_content filter, and the companion plugin is only scaffolded when a site has custom blocks or preserved JS. A plain static site with literal :) can emit no companion payload, produce no plugin, and still return a receipt claiming the policy was applied. Core gates every convert_smilies() call on the use_smilies option, so the site option is the owning primitive: setting it to false is honored everywhere the conversion would otherwise run, and the receipt only reports applied when the mutation actually happened.

CLI

--no-disable-smilies is accepted by import-website-artifact, import-url, and materialize-wordpress-site-plan to opt out of the default-on policy.

Tests

  • smoke-wordpress-site-plan-materializer.php: activating import sets use_smilies to false and leaves convert_smilies( 'Hello :)' ) unchanged; non-activating import leaves the option untouched; receipt reports { requested, applied } for default-on, explicit opt-out, and activating cases.
  • smoke-website-artifact-import-input.php: disable_smilies defaults to true, and '1' / '0' coerce to bool.

Testing

Test 1: Default (flag on)

  1. Import a static site containing literal :) (for example <p>Hello :)</p>) with an activating import (wp static-site-importer import-url https://example.com --activate).
  2. Load the imported page on the frontend.
  3. Expected: page shows Hello :) as text, with no <img src=".../wp-includes/images/smilies/..."> markup in the rendered HTML.

Test 2: Opt out

  1. Repeat the same import with --no-disable-smilies.
  2. Expected: :) renders as the external emoji image, core default behavior unchanged.

Automated: all smokes pass under npm test (42 passed, 0 failed; wordpress-runtime tests require a live WP and are skipped in the fast lane). npm run test:inventory is clean.

@chubes4 chubes4 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on. The explicit import option and receipt evidence are the right direction, but the companion-plugin implementation does not reliably apply the policy.

The blocking issue is that ArtifactCompiler emits companion_plugin_payload only when custom blocks or runtime islands require one. Static_Site_Importer_Theme_Generator therefore copies disable_smilies into a plugin payload only when an unrelated companion payload already exists. A simple static site containing literal :) can have no companion payload, produce no plugin, and still return a receipt claiming runtime_policy.disable_smilies: true.

The generated callback also removes only the the_content filter. Core gates every convert_smilies() call on the use_smilies option, including direct template/template-part calls and the excerpt, caption, comment, and widget filters. The site option is the existing owning WordPress primitive and avoids making this policy depend on generated-plugin intent.

Could you revise this around the existing site-plan activation path?

  1. Keep disable_smilies in the normalized import input and materialization ability contract.
  2. Remove the companion-plugin payload/scaffold changes for this policy.
  3. When materialization activates the imported theme and the policy is enabled, call update_option( 'use_smilies', false ). A non-activating import should not mutate the currently active site's global option.
  4. Make receipt evidence distinguish requested policy from applied policy. It should not report the policy as applied when activate is false or when no runtime mutation occurred.
  5. Cover a plain artifact that emits no companion payload, an activating import that leaves convert_smilies( 'Hello :)' ) unchanged, a non-activating import that leaves the existing option untouched, and the default-policy decision.

One product decision also needs to be explicit: issue #780 says imported literal smilies remain text, but this PR defaults the option to false, so ordinary imports retain the bug. If this remains opt-in, callers must wire it deliberately and the issue's default acceptance is not complete.

The branch currently conflicts with main and has no CI results, so it will also need a refresh after the architectural change.

AI assistance: OpenAI GPT-5.6 Sol via OpenCode reviewed the current SSI, Blocks Engine, and WordPress core paths and helped draft these findings. Chris Huber is responsible for the review.

faisalahammad added a commit to faisalahammad/static-site-importer that referenced this pull request Aug 3, 2026
… option

Imported literal emoticon text (:) was lost to WP core smilie conversion.
Set the owning site option use_smilies=false when materialization activates
the imported theme, instead of a companion-plugin filter that is never
scaffolded for plain artifacts. The receipt reports
runtime_policy.disable_smilies as {requested, applied}; applied is only
true when the activation-time update_option actually ran. Defaults on so
ordinary imports keep literal text.

Refs Automattic#780 Automattic#788
@faisalahammad
faisalahammad force-pushed the fix/780-disable-smilies branch from 68dda7e to 48ab1a3 Compare August 3, 2026 08:12

@faisalahammad faisalahammad left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 48ab1a3. Revised around the site-plan activation path as requested:

  1. Option kept in contractdisable_smilies stays in the normalized import input (website-artifact-import-input schema, default, bool coercion) and the materialize-wordpress-site-plan ability schema.
  2. Companion-plugin approach removed — no disable_smilies in companion payload/scaffold or theme-generator codegen. The policy no longer depends on ArtifactCompiler emitting a payload.
  3. Owning primitive — on an activating import with the policy enabled, materialization calls update_option( 'use_smilies', false ) inside the existing activation gate (while activating block in materialize_prepared()). A non-activating import never touches the active site's global option.
  4. Requested vs applied receipt evidencecompleted.runtime_policy.disable_smilies is now { "requested": bool, "applied": bool }. applied is only ever set in the same branch as the update_option write (so it is true only when a runtime mutation actually ran), and the write itself is fail-closed (disable_smilies_not_applied receipt on update_option returning false).
  5. Smokessmoke-wordpress-site-plan-materializer.php covers: non-activating default (requested true / applied false / option untouched), non-activating with explicit false, activating default (option flipped, convert_smilies('Hello :)') returns literal text, requested+applied true), activating with explicit false. The plain-artifact case (no companion payload) is exercised because the materializer smoke never scaffolds a companion plugin.

Product decision is now explicit and defaulted per issue #780: the policy defaults to true, so ordinary activating imports disable smilie conversion and literal :) stays text. Opt-out is --no-disable-smilies (CLI) or disable_smilies: false (ability/REST).

Branch was reset onto current main and refreshed (the stale 66-behind history was dropped entirely — the old approach is gone, not reverted-in-place).

Tests: npm test 42 passed / 0 failed, npm run test:inventory clean, targeted smokes green.

@chubes4

chubes4 commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

The revised site-option direction has one blocking idempotency defect: WordPress update_option() returns false both on failure and when the stored value is already false. After the first activating import, every subsequent activating import therefore returns disable_smilies_not_applied even though the requested runtime state is already applied. The smoke stub always returns true, so it does not cover core's unchanged-value contract.

Please treat an existing get_option( 'use_smilies' ) === false as applied, and after a write verify the stored value rather than using the update_option() return value as the state oracle. Add a repeated activating-import assertion with use_smilies already false. The branch also still conflicts with current main.

I independently replayed the four-surface #780 fixture against current main and the settled WP Codebox capture stack. Preserving literal smilie text during post-content materialization removes the external 1f642.svg request and restores the exact established baseline while all four editor surfaces remain clean. The tested alternative and receipt-provenance contract are available at Automattic:fix/780-disable-smilies; its repository-native checks are php tests/smoke-wordpress-site-plan-materializer.php and npm test. This narrower option avoids mutating unrelated site-wide content and works for non-activating materialization, so I recommend comparing that ownership boundary while resolving the branch.

AI assistance: OpenAI gpt-5.6-sol via OpenCode inspected WordPress option semantics, ran the four-surface replay, and helped draft this review. Chris Huber is responsible for the findings.

@faisalahammad

Copy link
Copy Markdown
Contributor Author

Addressed. The idempotency defect is fixed and covered:

  1. Already-false treated as appliedmaterialize_prepared() now gates the write on the existing value: if ( false !== get_option( 'use_smilies', false ) ) before calling update_option( 'use_smilies', false ). When the option is already false, no write runs and the receipt records applied: true (the runtime state is already what the policy requests).

  2. update_option() return is no longer the state oracle — the stored value is checked before the write, so core's "unchanged value → returns false" contract can no longer masquerade as failure. A genuine write failure still fails closed with disable_smilies_not_applied.

  3. Repeated activating-import smoke coveragetests/smoke-wordpress-site-plan-materializer.php now:

    • Has an honest update_option stub that returns false when the stored value is unchanged (core semantics, false on both failure and already-false), plus the previously-missing get_option stub.
    • Adds repeated-activating-import-* assertions: an activating import with use_smilies already false must complete (status completed), record { requested: true, applied: true }, and leave the option false.

    Coverage proof (not in the diff): reverting the production guard to the old update_option-return conditional makes the new repeated-activating-import-* assertions fail; with the guard restored, all assertions pass. Regression cases stay green — non-activating imports never touch the option, explicit disable_smilies: false still yields { requested: false, applied: false }.

Branch was reset onto current main (0 behind) — the conflict flag from last week is gone.

Tests: npm test 42 passed / 0 failed; php tests/smoke-wordpress-site-plan-materializer.php and php tests/smoke-website-artifact-import-input.php (138 assertions) green.

One follow-up for a future pass (pre-existing, not introduced here, not blocking): on a fresh site where the use_smilies option row has never been written, get_option( 'use_smilies', false ) returns false for the missing key, the write path runs, and update_option can return false for the same missing-key reason — so the first ever activation could still fail once, then succeed once the row exists. Real sites that have rendered smilies always have the row, which is the common case.

… option

Imported literal emoticon text (:) was lost to WP core smilie conversion.
Set the owning site option use_smilies=false when materialization activates
the imported theme, instead of a companion-plugin filter that is never
scaffolded for plain artifacts. The receipt reports
runtime_policy.disable_smilies as {requested, applied}; applied is only
true when the activation-time update_option actually ran. Defaults on so
ordinary imports keep literal text.

Refs Automattic#780 Automattic#788
@faisalahammad
faisalahammad force-pushed the fix/780-disable-smilies branch from fb25e52 to 9bd21ca Compare August 3, 2026 19:14
@chubes4
chubes4 merged commit 8629ecb into Automattic:main Aug 3, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disable WordPress smilie mutation for imported static content

2 participants