feat: lit/library-make-global – node to global module, all or nothing (E10)

- NewLibraryEntry: spec and creation of a new library entry, shared by
  lit/library-save and lit/library-make-global (NewDraft, P7, StoredNodeGuard)
- UsageMatcher::referencesAny() (ruling Q7): a global entry never names any
  entry in any spelling Divi renders (P23/P24 forms, Divi 4 shortcodes inside
  Divi 5 values, values casting to 0); used by library-save scope global and
  make-global
- make-global: entry created, node replaced by GlobalInstance::of, page through
  DiviWriter::commit; refused or rolled-back page deletes the entry, a partial
  page write keeps it (the page may show it); sources are pages and posts only

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-27 21:35:48 +02:00
co-authored by Claude Opus 5.5
parent 4f70886006
commit 44c1804c4c
9 changed files with 830 additions and 101 deletions
@@ -115,6 +115,45 @@ final class UsageMatcherTest extends TestCase
self::assertSame($shows, UsageMatcher::shows($content, 123));
}
/**
* Ruling Q7: referencesAny() refuses every reference Divi renders, to any entry – what shows() finds for
* some ID, and the values that cast to 0 (the newest layout – possibly the entry being created).
*
* @dataProvider contents
*/
public function testReferencesAnyFindsEveryReferenceToAnyEntry(string $content, bool $shows): void
{
$any = UsageMatcher::shows($content, 123) || UsageMatcher::shows($content, 124)
|| UsageMatcher::shows($content, 1234) || UsageMatcher::shows($content, 1230);
self::assertSame($any, UsageMatcher::referencesAny($content));
}
/** @dataProvider wildcards */
public function testReferencesAnyFindsValuesThatCastToZero(string $content): void
{
self::assertTrue(UsageMatcher::referencesAny($content));
}
/** @return array<string, array{string, bool}> */
public static function anyContents(): array
{
return [
'divi 5 non-numeric id' => ['<!-- wp:divi/global-layout {"globalModule":"abc","blockName":"divi/text"} /-->', true],
'divi 5 numeric id' => ['<!-- wp:divi/global-layout {"globalModule":7} /-->', true],
'divi 5 textual id in raw text' => ['<p>"globalModule":"9"</p>', true],
'divi 4 other tag' => ['[et_pb_blurb global_module="55"][/et_pb_blurb]', true],
'divi 4 zero' => ['[et_pb_section global_module="0"][/et_pb_section]', false],
'plain text' => ['<!-- wp:divi/text {"content":{"innerContent":{"desktop":{"value":"<p>Global</p>"}}}} /-->', false],
];
}
/** @dataProvider anyContents */
public function testReferencesAnyFailsClosed(string $content, bool $references): void
{
self::assertSame($references, UsageMatcher::referencesAny($content));
}
public function testTheEscapeBytesAreReal(): void
{
self::assertSame(6, strlen(self::u('0067')));