Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ All notable changes to `ichava/core` follow [Keep a Changelog](https://keepachan

### Changed

- **The Twemoji pack is `ichava/icon-sets-twemoji`** (owner, 2026-11-05: forward only, no aliases). The emoji pack ships Twemoji only and was renamed from `ichava/icon-sets-emoji`. `icon-sets.json` lists it under key `twemoji` (was `emoji`), package and repository `ichava/icon-sets-twemoji` and title `Twemoji`, so `ichava::ichava-core.install twemoji` installs it and `emoji` is no longer a key; the OpenMoji entry's description follows that pack's `config.json`. The docs pack table, architecture diagrams, pack-docs link and `check-updates` example name the new package, and the registration golden's fixture moved to `RegistrationGolden/twemoji` with the pack's current `config.json`. Stored ids `ichava/icon-sets-emoji::twemoji/...` no longer resolve; hosts delete them and re-seed the pack. `NoFormerTwemojiPackNameTest` fails if the old name appears anywhere in this repository outside this changelog (proven red on a planted line).
- **`icon-sets.json` records each pack's `visibility`** (`public` or `private`), synced by `bin/sync-icon-sets.php` from the repos API and kept when a response does not state it (F-02). Documented under *The pack catalogue* in `docs/tools/artisan-commands.md`. `tests/Unit/SyncIconSetsVisibilityTest.php`.
- **The generic Blade snippet uses a prefix this host registers**: the short one while `components.short_prefix` names one, `ichava-core` when it is off (`ComponentPrefixes::snippetPrefix()`).
- **`IconDriverInterface` states `SvgDriver`'s real API** (`render(IconData …)`, `load(string $path …)`), `SvgDriver` implements it, the provider binds it, and `IconRegistry`, `IconSvgReader` and `DirectoryIconDriver` depend on it (C-17). The interface's old directory methods (`all`, `has`, `setConfig`, `getConfig`, `setIconPath`, `getIconPath`) are gone from it; `DirectoryIconDriver` keeps them as its own API. `IconRegistry::driver()` returns the interface. `tests/Feature/IconDriverContractTest.php`.
Expand Down
14 changes: 7 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ flowchart TB
browser["<b>ichava/icon-browser</b><br/>REST API + Inertia/React browser<br/>middleware + browser views<br/>PRIVATE, licensed"]
tabler["<b>ichava/icon-sets-tabler</b><br/>6,202 icons"]
flag["<b>ichava/icon-sets-flag</b><br/>542 flags, 1x1 + 4x3"]
emoji["<b>ichava/icon-sets-emoji</b><br/>3,435 Twemoji icons"]
twemoji["<b>ichava/icon-sets-twemoji</b><br/>3,435 Twemoji icons"]
third["<b>3rd-party packs</b><br/>via ichava::icon-sets-package-scaffolder.make"]
meta["<i>ichava/ichava</i><br/>pre-split monolith, superseded by core<br/>ARCHIVED 2026-09-22, read-only"]
toolkit["<b>ichava/icon-sets-sync</b><br/>Docker-first sync tool<br/>refreshes vendored SVGs via PR<br/>(not installed by users)"]
Expand All @@ -40,11 +40,11 @@ flowchart TB
browser -- depends on --> core
tabler -- depends on --> core
flag -- depends on --> core
emoji -- depends on --> core
twemoji -- depends on --> core
third -- depends on --> core
toolkit -. CI refreshes .-> tabler
toolkit -. CI refreshes .-> flag
toolkit -. CI refreshes .-> emoji
toolkit -. CI refreshes .-> twemoji
react -. replaces the SPA in .-> browser

classDef engine fill:#1f6feb,stroke:#1f6feb,color:#fff
Expand All @@ -54,7 +54,7 @@ flowchart TB
classDef tooling fill:#bf8700,stroke:#bf8700,color:#fff
class core engine
class browser http
class tabler,flag,emoji,third pack
class tabler,flag,twemoji,third pack
class meta,motion,react other
class toolkit tooling
```
Expand Down Expand Up @@ -106,16 +106,16 @@ classDiagram
class FlagIcons_IconsServiceProvider["IconsServiceProvider"] {
<<Ichava\IconSetsFlag\Providers>>
}
class EmojiSets_IconsServiceProvider["IconsServiceProvider"] {
<<Ichava\IconSetsEmoji\Providers>>
class Twemoji_IconsServiceProvider["IconsServiceProvider"] {
<<Ichava\IconSetsTwemoji\Providers>>
}
PackageServiceProvider <|-- IchavaServiceProvider
PackageServiceProvider <|-- SupportServiceProvider
SupportServiceProvider <|-- IconPackServiceProvider
PackageServiceProvider <|-- IconBrowserServiceProvider
IconPackServiceProvider <|-- TablerIcons_IconsServiceProvider
IconPackServiceProvider <|-- FlagIcons_IconsServiceProvider
IconPackServiceProvider <|-- EmojiSets_IconsServiceProvider
IconPackServiceProvider <|-- Twemoji_IconsServiceProvider
```

**Every pack's provider is called `IconsServiceProvider`.** The short name is a constant across the
Expand Down
2 changes: 1 addition & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ the repositories block in step 1, then require it. Some of the public ones:
|---|---|---|
| `ichava/icon-sets-tabler` | 6,238 | `https://github.com/ichava/icon-sets-tabler` |
| `ichava/icon-sets-flag` | 542 | `https://github.com/ichava/icon-sets-flag` |
| `ichava/icon-sets-emoji` | 3,435 | `https://github.com/ichava/icon-sets-emoji` |
| `ichava/icon-sets-twemoji` | 3,435 | `https://github.com/ichava/icon-sets-twemoji` |
| `ichava/icon-sets-openmoji` | 8,568 | `https://github.com/ichava/icon-sets-openmoji` |

```bash
Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/use-an-icon-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Each pack's repository has a `docs/` directory:

- [`ichava/icon-sets-tabler/docs/`](https://github.com/ichava/icon-sets-tabler/tree/main/docs)
- [`ichava/icon-sets-flag/docs/`](https://github.com/ichava/icon-sets-flag/tree/main/docs)
- [`ichava/icon-sets-emoji/docs/`](https://github.com/ichava/icon-sets-emoji/tree/main/docs)
- [`ichava/icon-sets-twemoji/docs/`](https://github.com/ichava/icon-sets-twemoji/tree/main/docs)

Look there for stroke widths, variant lists, attribution, and any pack-specific behaviour.

Expand Down
2 changes: 1 addition & 1 deletion docs/tools/artisan-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ Reports whether any registered icon pack is behind its upstream source. Reads ea

```bash
php artisan ichava::ichava-core.check-updates # table for every pack
php artisan ichava::ichava-core.check-updates --package=ichava/icon-sets-emoji # restrict to one pack
php artisan ichava::ichava-core.check-updates --package=ichava/icon-sets-twemoji # restrict to one pack
php artisan ichava::ichava-core.check-updates --format=json # machine-readable
php artisan ichava::ichava-core.check-updates --fail-on-stale # exit 1 if any pack is behind
```
Expand Down
10 changes: 5 additions & 5 deletions icon-sets.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@
"visibility": "public"
},
{
"key": "emoji",
"package": "ichava/icon-sets-emoji",
"repository": "ichava/icon-sets-emoji",
"title": "Emoji Sets",
"key": "twemoji",
"package": "ichava/icon-sets-twemoji",
"repository": "ichava/icon-sets-twemoji",
"title": "Twemoji",
"description": "Twemoji, Twitter/X's open-source emoji set, as SVG. Unicode 17 / Emoji 17 spec. Categorised by Unicode CLDR groups. OpenMoji is in ichava/icon-sets-openmoji.",
"license": "MIT (code) / CC-BY 4.0 (Twemoji assets)",
"icon_count": 3435,
Expand All @@ -64,7 +64,7 @@
"package": "ichava/icon-sets-openmoji",
"repository": "ichava/icon-sets-openmoji",
"title": "OpenMoji",
"description": "OpenMoji emoji in two sets (colour and black outline), Unicode emoji 16.0, grouped by Unicode CLDR category. Moved verbatim from ichava/icon-sets-emoji.",
"description": "OpenMoji emoji in two sets (colour and black outline), Unicode emoji 16.0, grouped by Unicode CLDR category. Moved verbatim from ichava/icon-sets-twemoji, which now ships Twemoji only.",
"license": "MIT (code) / CC-BY-SA 4.0 (OpenMoji assets)",
"icon_count": 8568,
"variants": [
Expand Down
6 changes: 3 additions & 3 deletions src/Constants/JsonConfigConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,8 @@ public static function getType(): string
* is the site of the project whose icons the pack vendors, and it is the
* only place that fact is recorded in a human-facing form.
*
* Returns '' when the pack has no single upstream -- `icon-sets-emoji`
* vendors two projects across three sets and omits the key deliberately.
* Returns '' when the pack omits the key -- `icon-sets-twemoji` does, and
* names its upstream, with its URL, in `metadata.authors` instead.
*
* Example: https://tabler-icons.io/
*/
Expand Down Expand Up @@ -495,7 +495,7 @@ public static function getUpstreamCdnUrls(): array
*
* { "type": "npm", "package": "...", "source_path": "..." }
* { "type": "github-archive", "archive_url": "...", "source_path": "..." }
* { "type": "recipe", "recipe": "emoji-sets" }
* { "type": "recipe", "recipe": "twemoji-sets" }
* { "type": "url", "version_field": "version" }
*
* @return array<string, mixed>|null
Expand Down
6 changes: 3 additions & 3 deletions src/Events/IconPackUpdateAvailable.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public function __construct(
/**
* Which source under the pack's `upstream` block fired the
* event. "primary" for the canonical source; the value of
* `additional_sources[*].name` for secondary trackers (e.g.
* emoji-sets dispatches one event for `primary` (Twemoji) and
* one for `openmoji` whenever either upstream moves).
* `additional_sources[*].name` for secondary trackers (a pack
* that tracks a second upstream dispatches one event for
* `primary` and one for that source whenever either moves).
*/
public string $sourceName = 'primary',
) {}
Expand Down
2 changes: 1 addition & 1 deletion src/Services/SvgProcessingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ final class SvgProcessingService
* 4: the sanitiser drops stray text and CDATA directly under `<svg>` and
* `<g>` (icon-sets-sync#85), which no config hash sees. Measured 2026-11-04
* across 142,247 pack SVGs, 2 outputs changed for it (OpenMoji's 1F684
* high-speed train in icon-sets-emoji, eos-icons' link-off in
* high-speed train, now in icon-sets-openmoji, eos-icons' link-off in
* icon-sets-bundled). The 34 that changed for the new `display` and `color`
* attributes are re-keyed by the attribute hash on their own.
*/
Expand Down
4 changes: 2 additions & 2 deletions tests/Feature/Commands/InstallCommandCharacterizationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function bindInstallCatalog(array|Throwable $sets): void
'Tabler Icons (',
' icons, outline + filled) — ○ not installed · ○ not seeded',
'Flag Icons (',
'Emoji Sets (',
'Twemoji (',
'Latest release: ',
"Require '{$target}' via Composer? (yes/no) [yes]",
'Operation cancelled.',
Expand All @@ -96,7 +96,7 @@ function bindInstallCatalog(array|Throwable $sets): void
[$exit, $display] = $this->runCommand(INSTALL_COMMAND, ['set' => 'nope']);

$this->assertSame(1, $exit);
$this->assertDisplayContains($display, ["✗ Unknown icon set 'nope'.", 'Available: tabler, flags, emoji']);
$this->assertDisplayContains($display, ["✗ Unknown icon set 'nope'.", 'Available: tabler, flags, twemoji']);
});

it('fails when the catalog cannot be loaded', function (): void {
Expand Down
4 changes: 2 additions & 2 deletions tests/Feature/PackConfigShapeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* ever got `supportsCategories`, and IconSetBuilder::buildIconPath() dropped the
* middle segment of a three-segment path: `glyphs/round/dot` was looked up as
* `files/glyphs/dot.svg`. That made every icon one directory deeper than the
* first unreachable (15,393 in icon-sets-bundled, all 10,567 in
* icon-sets-emoji).
* first unreachable (15,393 in icon-sets-bundled, and all 10,567 in the
* emoji pack before OpenMoji moved out of it).
*
* The top-level shape is not read at all: the estate is forward-only, and the
* fallback that kept a pack written against the old lookup working was
Expand Down
92 changes: 92 additions & 0 deletions tests/Unit/NoFormerTwemojiPackNameTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| The Twemoji pack is named by its current name
|--------------------------------------------------------------------------
|
| The emoji pack was renamed to `ichava/icon-sets-twemoji` (2026-11-05),
| forward only: no alias, no fallback catalogue key, no `replaces`. Core names
| it in its catalogue, docs, comments and fixtures, so the old name has no
| business here except in the history that records it:
|
| - CHANGELOG.md, whose entries keep the name the pack had at the time;
| - one comment in .github/workflows/changelog.yml, byte-identical across
| the estate, which cites a past CHANGELOG defect by the pack's name then.
|
| The former name is assembled from parts so this file does not match itself.
|
*/

function core_former_twemoji_pack_name(): string
{
return 'icon-sets-' . 'emoji';
}

/** @return list<string> paths relative to the repository root */
function core_former_name_scanned_files(): array
{
$root = dirname(__DIR__, 2);
$skip = ['.git', 'vendor', 'node_modules', '.phpstan.cache', '.phpunit.cache', '.idea', 'build'];
$files = [];

$iterator = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS),
static fn (SplFileInfo $file): bool => ! ($file->isDir() && in_array($file->getFilename(), $skip, true)),
),
);

foreach ($iterator as $file) {
if ($file instanceof SplFileInfo && $file->isFile()) {
$files[] = substr($file->getPathname(), strlen($root) + 1);
}
}

sort($files);

return $files;
}

it('names the Twemoji pack by its current name everywhere outside history', function (): void {
$root = dirname(__DIR__, 2);
$former = core_former_twemoji_pack_name();
$files = core_former_name_scanned_files();
$offenders = [];

foreach ($files as $path) {
if ($path === 'CHANGELOG.md') {
continue;
}

$lines = file($root . '/' . $path, FILE_IGNORE_NEW_LINES) ?: [];

foreach ($lines as $number => $line) {
if (! str_contains($line, $former)) {
continue;
}

if ($path === '.github/workflows/changelog.yml' && str_contains($line, $former . ' had [0.2.6] filed')) {
continue;
}

$offenders[] = $path . ':' . ($number + 1);
}
}

// src, tests, fixtures, docs and workflows: 484 tracked files on
// 2026-11-05. A scan that stops finding files reports a clean tree.
expect(count($files))->toBeGreaterThan(400)
->and($offenders)->toBe([], 'the former Twemoji pack name is back in: ' . implode(', ', $offenders));
});

it('catalogues the Twemoji pack under its current name and key', function (): void {
$sets = json_decode((string) file_get_contents(dirname(__DIR__, 2) . '/icon-sets.json'), true, flags: JSON_THROW_ON_ERROR)['sets'];
$twemoji = array_values(array_filter($sets, static fn (array $set): bool => $set['package'] === 'ichava/icon-sets-twemoji'));

expect($twemoji)->toHaveCount(1)
->and($twemoji[0]['key'])->toBe('twemoji')
->and($twemoji[0]['repository'])->toBe('ichava/icon-sets-twemoji');
});
94 changes: 47 additions & 47 deletions tests/fixtures/RegistrationGolden.json
Original file line number Diff line number Diff line change
Expand Up @@ -442,52 +442,6 @@
"version": "0.1.0"
}
},
"<root>/tests/fixtures/RegistrationGolden/emoji": {
"aliases": [],
"metadata": {
"base_path": "<root>/tests/fixtures/RegistrationGolden/emoji",
"blade_component": null,
"category_kinds": [],
"declared_kind": "icon",
"description": "Twemoji, Twitter/X's open-source emoji set, as SVG. Unicode 17 / Emoji 17 spec. Categorised by Unicode CLDR groups. OpenMoji is in ichava/icon-sets-openmoji.",
"homepage": null,
"icon_set_name": "ichava/icon-sets-emoji",
"item_kinds": [],
"keywords": [
"emoji",
"twemoji",
"svg",
"unicode",
"bundle"
],
"kind": "icon",
"kind_issues": [],
"kind_rules": [],
"labels": [],
"license": "MIT (code) / CC-BY 4.0 (Twemoji assets)",
"name": "Emoji Sets",
"package_name": "ichava/icon-sets-emoji",
"prefix": "emoji",
"provider_class": null,
"repository": "https://github.com/ichava/icon-sets-emoji",
"theme_variants": [],
"total": 0,
"variant_metadata": {
"default": {
"name": "Default",
"slug": "default",
"description": "Standard Twemoji style",
"default": true,
"icon_suffix": "",
"display_order": 1,
"attributes": [],
"color_scheme": "adaptive"
}
},
"vendor": "ichava",
"version": "0.1.0"
}
},
"<root>/tests/fixtures/RegistrationGolden/extras": {
"aliases": [],
"metadata": {
Expand Down Expand Up @@ -1192,7 +1146,7 @@
"blade_component": null,
"category_kinds": [],
"declared_kind": "icon",
"description": "OpenMoji emoji in two sets (colour and black outline), Unicode emoji 16.0, grouped by Unicode CLDR category. Moved verbatim from ichava/icon-sets-emoji.",
"description": "OpenMoji emoji in two sets (colour and black outline), Unicode emoji 16.0, grouped by Unicode CLDR category. Moved verbatim from ichava/icon-sets-twemoji, which now ships Twemoji only.",
"homepage": "https://openmoji.org/",
"icon_set_name": "ichava/icon-sets-openmoji",
"item_kinds": [],
Expand Down Expand Up @@ -1483,6 +1437,52 @@
"version": "0.1.0"
}
},
"<root>/tests/fixtures/RegistrationGolden/twemoji": {
"aliases": [],
"metadata": {
"base_path": "<root>/tests/fixtures/RegistrationGolden/twemoji",
"blade_component": null,
"category_kinds": [],
"declared_kind": "icon",
"description": "Twemoji, Twitter/X's open-source emoji set, as SVG. Twemoji 15, on the Unicode emoji 16.0 taxonomy. Categorised by Unicode CLDR groups. OpenMoji is in ichava/icon-sets-openmoji.",
"homepage": null,
"icon_set_name": "ichava/icon-sets-twemoji",
"item_kinds": [],
"keywords": [
"emoji",
"twemoji",
"svg",
"unicode",
"bundle"
],
"kind": "icon",
"kind_issues": [],
"kind_rules": [],
"labels": [],
"license": "MIT (code) / CC-BY 4.0 (Twemoji assets)",
"name": "Twemoji",
"package_name": "ichava/icon-sets-twemoji",
"prefix": "twemoji",
"provider_class": null,
"repository": "https://github.com/ichava/icon-sets-twemoji",
"theme_variants": [],
"total": 0,
"variant_metadata": {
"default": {
"name": "Default",
"slug": "default",
"description": "Standard Twemoji style",
"default": true,
"icon_suffix": "",
"display_order": 1,
"attributes": [],
"color_scheme": "adaptive"
}
},
"vendor": "ichava",
"version": "0.1.0"
}
},
"<root>/tests/fixtures/SpecPack/resources/assets/svg": {
"aliases": [],
"metadata": {
Expand Down
Loading
Loading