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
6 changes: 3 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ All notable changes to `ichava/core` follow [Keep a Changelog](https://keepachan
- **`IchavaConstants::PACK_COMPOSER_TYPE`** (`ichava-icon-set`), the composer `type` a pack declares so discovery can find it.
- **Kind resolution covers every D6 row** (K1, contract §1–2). Besides a category's `kind` and `package.kind`, a pack can now declare kind per item (`metadata.data.items.<item path>.kind`) and by ordered glob rule (`metadata.data.kinds.rules[]`, first match wins), and a host can override any of it in `ichava.ichava-core.kinds.overrides` (`'<vendor/pack | *>::<glob or exact item path>' => kind`, exact keys before globs). The order is host, item, rule, category, pack, then `icon`. New `Support\KindGlob` (`*`, `?`, whole-segment `**`; the contract's vector table is its test), `Support\KindResolver`, `Enums\KindSource` and the `final readonly` DTO `Data\KindResolution`; `PackConfigData::items()`/`kindRules()` read the new keys from the documented shape and the top level. `IconRegistry::kindOf()`, the renderer's size budget and `IconClassifier::kind()` all resolve through `KindResolver`; `IconRegistry::kindFor()` keeps its category/pack behaviour for its callers. A v0.1 `config.json` with no kind keys loads exactly as before. `tests/Unit/KindGlobTest.php`, `tests/Feature/KindResolutionTest.php`.
- **Each row records which rule decided its kind.** Migration `2026_11_04_000001_add_kind_source_to_ichava_icons` adds `kind_source` (string(16), default `default`) and `kind_rule` (string(255), nullable), additive on all four drivers and reversible. The seeder and the watcher write both from `IconClassifier::resolution()`, and both join change detection, so a new override re-files unchanged files without `--force`. `Icon::kindSource()` and the `kind_source` cast. `transformIcon()` adds `kind_source`; `kind_rule` stays out of the API because it can carry host config.
- **`Ichava::kindOf()`, `Ichava::kindResolution()` and `Ichava::canonicalId()`** on the facade (K5).
- **`Ichava::kindOf()` and `Ichava::kindResolution()`** on the facade (K5).
- **Per-kind counts per pack** (contract §5.2): `getStatistics()['by_package_kind']` (`[]` when nothing is seeded) and `by_kind` on each package in `getFilters()`, within the current filters. `getIcons()` takes several kinds (`'kind' => ['icon', 'illustration']`), OR-ed, duplicates collapsed.
- **`artisan about` reports kinds** (K3/D4): `Icons by kind`, `Default kind` (the first reader of `IconKind::default()`, K2/D3), `Kind sources` and `Kind overrides`.
- **Doctor kind and alias checks** (contract §7): `kind.invalid_value`, `kind.invalid_glob`, `kind.override_invalid`, `kind.rule_unused`, `kind.item_missing`, `kind.drift` (with the seed command to run), `kind.non_default`, `alias.deprecated_ids` and `alias.replacement_missing`, all warnings. `--format=json` now prints every doctor check as `{id, status, count, detail}`. New `Services\KindDoctor`. `tests/Feature/KindDoctorTest.php`.
- **Doctor kind checks** (contract §7): `kind.invalid_value`, `kind.invalid_glob`, `kind.override_invalid`, `kind.rule_unused`, `kind.item_missing`, `kind.drift` (with the seed command to run), and `kind.non_default`, all warnings. `--format=json` now prints every doctor check as `{id, status, count, detail}`. New `Services\KindDoctor`. `tests/Feature/KindDoctorTest.php`.
- **A kind heuristic that suggests and never applies** (D6 row 7, contract §3). `doctor --suggest-kinds[=default|all]` reads every SVG and suggests `illustration` for a default-row icon when three of `aspect`, `palette`, `bytes`, `elements` and `canvas` fire, one of them `bytes` or `elements`; a mostly suggested folder becomes one `<folder>/**` rule. Thresholds are `IchavaConstants::KIND_HEURISTIC_*`, shared with icon-sets-sync. Nothing writes a suggestion anywhere.
- **A set can move to another pack** (D9, contract §8). A pack declaring `package.replaces` takes over the listed item paths of another pack: with it installed, the old id resolves in the new pack wherever an id is parsed, each rewritten id is logged once per process as `[DEPRECATED]`, and the old pack's moved categories are not seeded. Without it nothing changes. New `Support\PackAliases` and `PackConfigData::replaces()`/`deprecatedCategories()`. `tests/Feature/PackAliasTest.php`.
- **`<x-ichava-core::icon>`, the vendor-scoped spelling of the icon component** (V-B11). Core registers the `ichava-core` Blade prefix over the same component classes, plus the `ichava-core::icon` alias, so `<x-ichava-core::icon>` and `<x-ichava::icon>` resolve the same class and render the same bytes. Additive: the bare `ichava` prefix and alias are unchanged. `IchavaServiceProvider::SCOPED_COMPONENT_PREFIX`. `tests/Feature/ComponentNamespaceDeferralTest.php` reads Blade's live namespace and alias maps, and fails on any new unscoped core alias.
- **A `dark` twin on the icon component** (V-B10). `<x-ichava::icon name="…" dark="…">` renders both SVGs in a `<span class="ichava-themed">`, with `ichava-theme-light`/`ichava-theme-dark` classes and a nonce-carrying `<style>` that shows one at a time. New config `theme_variants.dark_mode` (`media`, by `prefers-color-scheme`, or `class`, by a `.dark` ancestor; `ICHAVA_DARK_MODE`) and `theme_variants.auto_dark` (`ICHAVA_AUTO_DARK`, off), which renders the twin a pack's category declares when no `dark` is passed. New `IconRegistry::themeTwin()`. Without `dark` and with `auto_dark` off the output is byte-identical to before (golden files). `tests/Feature/IconComponentDarkVariantTest.php`.
- **`IconRenderer::role(null)` omits the `role` attribute** (V-B9). The default stays `role="img"`, byte for byte. `tests/Feature/IconRendererRoleTest.php`.
Expand Down Expand Up @@ -56,6 +55,7 @@ The estate is forward-only (owner, 2026-11-05: "never support legacy only forwar
- **The `ichava` container alias for `Ichava::class`.** The facade resolves `Ichava::class`; nothing in the estate or the demos called `app('ichava')`, `resolve('ichava')` or `make('ichava')`. Resolve `Ichava::class`, or use the facade.
- **`IconDiscoveryService::registerPackage()`**, documented as "for testing/legacy purposes". It wrote a cache key nothing read, and had no caller.
- **`IconPreferenceService`'s pre-taxonomy fallback.** The available categories and variants were read from `ichava_icons.category` / `variant` when `ichava_icon_terms` did not exist; core's first migration creates that table, so they are read from it alone.
- **Moving a set between packs (`package.replaces`, D9).** It existed so that `ichava/icon-sets-emoji::openmoji-*` ids kept resolving after OpenMoji moved to `ichava/icon-sets-openmoji`. The owner removed the emoji copies (2026-11-05), so nothing declares it any more. Gone with it: `Support\PackAliases`, `PackConfigData::replaces()` and `deprecatedCategories()`, `IconRegistry::canonicalId()`, `isReplaced()` and `packAliases()`, `Ichava::canonicalId()`, `IchavaLogger::deprecation()`, the seeder's and the watcher's skip of replaced items, and doctor's `alias.deprecated_ids` and `alias.replacement_missing`. A `package.replaces` key or a category's `deprecated` marker in `config.json` is now ignored. An OpenMoji id is `ichava/icon-sets-openmoji::openmoji-color/…`.
- `tests/Unit/NoDeprecatedSymbolsTest.php` fails if an `@deprecated` tag appears anywhere in `src/` (it asserts it read at least 100 files).

## [0.1.0] - TBD
Expand Down
2 changes: 1 addition & 1 deletion 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/>10,567 icons / 3 sets<br/>twemoji, openmoji-color, openmoji-black"]
emoji["<b>ichava/icon-sets-emoji</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 Down
15 changes: 0 additions & 15 deletions docs/kinds.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,21 +144,6 @@ A pack's constants class answers `getCategoryKind($category)` and `getPackageKin

The `kind` column defaults to `icon` and `kind_source` to `default`, which is also the backfill for rows seeded before either existed.

## A set that moved to another pack

A pack that takes over a set from another declares it, and the old pack marks the moved categories deprecated:

```json
"package": {
"name": "ichava/icon-sets-openmoji",
"replaces": [ { "package": "ichava/icon-sets-emoji", "paths": ["openmoji-color/**"], "since": "0.1.0" } ]
}
```

With the replacing pack installed, `ichava/icon-sets-emoji::openmoji-color/1f600` resolves to `ichava/icon-sets-openmoji::openmoji-color/1f600` wherever an id is parsed (Blade, the helper, render and `kindOf`), and `Ichava::canonicalId()` returns the new id for callers that look ids up themselves. Each rewritten id is logged once per process to the `ichava` channel as `[DEPRECATED]`. The old pack's moved categories are not seeded, so nothing is counted twice. Without the replacing pack the old id resolves in the old pack, as before.

Doctor's `alias.deprecated_ids` scans `app/`, `resources/views`, `resources/js` and `config/` for old ids and reports each `file:line` (up to 50); `alias.replacement_missing` warns when a deprecated set is installed and its replacement is not.

## Querying by kind

```php
Expand Down
28 changes: 13 additions & 15 deletions resources/lang/en/commands.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,21 +342,19 @@
'routes' => 'Browser routes mounted',
'orphans' => 'No orphaned icon rows',
'kinds' => [
'kind.invalid_value' => 'Kind values name a kind',
'kind.invalid_glob' => 'Kind rule patterns are valid',
'kind.override_invalid' => 'Host kind override keys are valid',
'kind.rule_unused' => 'Every kind rule and override matches an item',
'kind.item_missing' => 'Every kind item names a file',
'kind.drift' => 'Stored kinds match the current config',
'kind.non_default' => 'Items with a non-default kind',
'kind.suggestions' => 'Kind suggestions (heuristic, never applied)',
'alias.deprecated_ids' => 'No deprecated icon ids in the app',
'alias.replacement_missing' => 'Every deprecated set has its replacement installed',
'drift_fix' => ':count row(s) of :package were seeded under different kind rules. Re-seed: :command',
'non_default_by_source' => 'Non-default kinds by deciding row: :sources',
'suggestion_package' => 'Package',
'suggestion_path' => 'Path',
'suggestion_signals' => 'Signals',
'kind.invalid_value' => 'Kind values name a kind',
'kind.invalid_glob' => 'Kind rule patterns are valid',
'kind.override_invalid' => 'Host kind override keys are valid',
'kind.rule_unused' => 'Every kind rule and override matches an item',
'kind.item_missing' => 'Every kind item names a file',
'kind.drift' => 'Stored kinds match the current config',
'kind.non_default' => 'Items with a non-default kind',
'kind.suggestions' => 'Kind suggestions (heuristic, never applied)',
'drift_fix' => ':count row(s) of :package were seeded under different kind rules. Re-seed: :command',
'non_default_by_source' => 'Non-default kinds by deciding row: :sources',
'suggestion_package' => 'Package',
'suggestion_path' => 'Path',
'suggestion_signals' => 'Signals',
],
'passed' => '✅ Nothing is broken.',
'failed' => 'Something a working install needs is missing.',
Expand Down
1 change: 0 additions & 1 deletion src/Facades/IchavaFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
* @method static bool has(string $name, ?string $variant = null, ?string $category = null)
* @method static \Simtabi\Laranail\Ichava\Enums\IconKind kindOf(string $id)
* @method static \Simtabi\Laranail\Ichava\Data\KindResolution kindResolution(string $id)
* @method static string canonicalId(string $id)
* @method static \Simtabi\Laranail\Ichava\Drivers\SvgDriver driver()
* @method static \Simtabi\Laranail\Ichava\Ichava setDefaultSet(string $name)
* @method static void registerFromConfig(array<int|string, string|array<string, mixed>> $sets)
Expand Down
12 changes: 0 additions & 12 deletions src/Ichava.php
Original file line number Diff line number Diff line change
Expand Up @@ -370,18 +370,6 @@ public function kindResolution(string $id): KindResolution
return $this->registry->kindResolution($id);
}

/**
* The id an icon lives under now: an id whose set moved to another
* installed pack is rewritten to that pack, and any other id is returned
* as given.
*
* @param string $id Icon id, e.g. `ichava/icon-sets-emoji::openmoji-color/1f600`
*/
public function canonicalId(string $id): string
{
return $this->registry->canonicalId($id);
}

/**
* Direct access to the SvgDriver (load and render raw SVG files).
*/
Expand Down
12 changes: 0 additions & 12 deletions src/Jobs/SeedIconsJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
use Simtabi\Laranail\Ichava\Models\IconTerm;
use Simtabi\Laranail\Ichava\Enums\KindSource;
use Simtabi\Laranail\DbTools\Query\ChunkedWriter;
use Simtabi\Laranail\Ichava\Support\KindResolver;
use Simtabi\Laranail\Ichava\Services\IchavaLogger;
use Simtabi\Laranail\Ichava\Services\IconRegistry;
use Simtabi\Laranail\Ichava\Support\IconClassifier;
Expand Down Expand Up @@ -198,17 +197,6 @@ protected function processFiles(IchavaLogger $logger): array
continue;
}

// A set another installed pack has taken over (D9) is served from
// that pack. Seeding it here as well would count every icon twice,
// so it is left out, and any row from before the move goes the way
// a twin's does.
if ($registry->isReplaced($this->packageName, KindResolver::itemPath($relativePath))) {
$twinPaths[] = $relativePath;
$stats['skipped']++;

continue;
}

$resolution = $classifier->resolution($this->packageName, $relativePath);
$fileHash = md5_file($absolutePath);
$name = pathinfo($absolutePath, PATHINFO_FILENAME);
Expand Down
17 changes: 0 additions & 17 deletions src/Services/IchavaLogger.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,23 +124,6 @@ public function debug(string $message, array $context = []): void
Log::channel($this->channel)->debug($message, $this->enrichContext($context));
}

/**
* Log a use of something deprecated, such as an icon id whose set moved
* to another pack. Prepends [DEPRECATED] so the entries can be grepped
* out of the main ichava channel.
*
* @param string $message What was used and what replaces it
* @param array<string, mixed> $context Additional structured context
*/
public function deprecation(string $message, array $context = []): void
{
if (! $this->enabled) {
return;
}

Log::channel($this->channel)->warning("[DEPRECATED] {$message}", $this->enrichContext($context));
}

/**
* Log a security event with automatic request context enrichment.
*
Expand Down
Loading
Loading