diff --git a/CHANGELOG.md b/CHANGELOG.md index bff3fe3..def48b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to `ichava/core` follow [Keep a Changelog](https://keepachan ## [Unreleased] +## [0.1.0] - TBD + +First release of `ichava/core`. The repository is published as a single commit; the 0.1.x tags cut between 2026-10-23 and 2026-10-27 (v0.1.0 to v0.1.3) are folded into this release, and their notes are kept below under *Archived history*. + ### Added - **One pack model, with a simple and an advanced path** (Proposal 2). Every set now registers as a `Data\PackDefinition` through `IconRegistry::register()`, built by the fluent `Support\Pack\PackBuilder` from a `config.json` (`PackBuilder::fromDirectory()`), from code (`PackBuilder::make('vendor/package', $filesPath)`), or both. Each builder method writes a key an existing reader consumes (`prefix`, `about`, `variant`, `defaultVariant`, `categories`, `kind`, `categoryKind`, `kindRule`, `itemKind`, `themeTwin`, `alias`/`aliases`, `defaultClass`, `defaultAttributes`, `fallback`, `providedBy`, `component`, `metadata`, `withOptions`, `when`). Fluent scalars replace the file's, maps merge per key, and fluent kind rules come first. `fromDirectory()`, `package()` and `custom-icons` all delegate to it, and a golden snapshot over 31 pack directories (19 estate packs and every fixture) proves registration is unchanged. `IconRegistry::definition()`/`definitionAt()` return what a set registered with, and a pack's constants class reads it through `Support\Pack\PackConfigReader`, so it sees the builder's overrides. `docs/tools/pack-registration.md`. `tests/Feature/PackBuilderTest.php`, `RegistrationGoldenTest.php`, `JsonConfigConstantsReadsDefinitionTest.php`. @@ -28,24 +32,22 @@ All notable changes to `ichava/core` follow [Keep a Changelog](https://keepachan - **`KindResolver::declaredResolution()`**, the resolver's category, pack and default rows as one entry point. `KindResolver::resolve()` ends in it, and `JsonConfigConstants::getCategoryKind()`/`getPackageKind()` now delegate to it, so a pack's constants class cannot disagree with the resolver. Same answers as before. `tests/Feature/JsonConfigKindDelegationTest.php`. - **A test that a pack installs by being required** (W1): a fixture `installed.json` names a pack provider in `extra.laravel.providers`, and the pack is registered and seeded with no registration call. `tests/Feature/PackDiscoveryWiringTest.php`. -### Fixed - -- **The nightly catalogue sync updates the installation guide's pack table with the catalogue.** `bin/sync-icon-sets.php` rewrote `icon-sets.json` alone, so a count change opened a pull request that `InstallationPackTableTest` failed: #179 moved tabler to 6,287 and openmoji to 8,990 while `docs/installation.md` still said 6,257 and 8,568. After writing the catalogue, the script now sets each table row's count from it with `withPackTableCounts()`, and the workflow commits both files. Rows, order and repositories are left as written, so a set added or removed still fails the table test. `tests/Unit/SyncIconSetsDocTableTest.php` covers it (proven red by leaving the counts alone). -- **The nightly catalogue sync no longer puts Twemoji's pre-split values back.** `bin/sync-icon-sets.php` reads each pack at its latest tag, and `icon-sets-twemoji`'s `v0.1.0` still holds OpenMoji, so #176 proposed 10,567 "Emoji Sets" over the recorded 3,435 "Twemoji". A set in `icon-sets.json` may now carry a `hold` (`tag`, `commit`, `reason`): while that tag is the latest release at that commit the sync keeps the set's snapshot, and the hold drops itself once a newer tag is cut or the tag moves. The twemoji set is held at `v0.1.0`. `tests/Unit/SyncIconSetsHoldTest.php` covers it (proven red by ignoring the commit). -- **A pack listed with the application boots again.** `PackConfigCache` resolved the cache store when it was built, and a pack's constants class builds it while the providers are still registering, before the store exists, so every pack failed to boot with `Target class [cache] does not exist` (found running the packs against #172). The store is now resolved when it is used. Core's own pack tests registered their fixture after boot, which is why none saw it; `tests/Feature/PackBootsInProviderListTest.php` lists the fixture with the application. -- **`InformationService::discoverPackages()` finds the installed packs.** It globbed `vendor/*/ichava-*`, which matches no pack (packs install as `vendor/ichava/icon-sets-*`), and reported `registered` through `IconRegistry::has()`, which looks up an icon, not a package, so `ichava::ichava-core.info discover` listed nothing. It now asks `Composer\InstalledVersions` for every package of type `ichava-icon-set` and reports `registered` through `IconRegistry::isRegistered()`. The signature and return shape are unchanged. A pack is identified by its composer type and nothing else (owner, 2026-11-05): the `vendor/*/ichava-*` directory glob is removed, since no pack in the estate matched it, and the `platform/ichava` scan now keeps only a directory whose composer.json declares `ichava-icon-set`, keyed by that file's `name` instead of `ichava/`. A checkout there without the type is no longer listed. `tests/Feature/InstalledPackDiscoveryTest.php`. -- **The installation guide no longer says core is on Packagist.** Nothing in the family is (`repo.packagist.org` answers 404 for core, every pack and the four `laranail/*` packages). `docs/installation.md` step 1 is now the one place that lists the repositories an application adds, `ichava/core` included; the README and every pack link to it. -- **The US and UM flags keep their stars.** Both draw the fifty stars as one `` repeated by `marker-mid="url(#us-a)"`, and `resources/security/svg-policy.json` did not allow ``, so the sanitiser removed it and the reference pointed at nothing. The policy now allows `` and its geometry (`refX`, `refY`, `markerWidth`, `markerHeight`, `orient`, `markerUnits`), the same bytes as ichava/icon-sets-sync#91. Scripts, `on*` handlers and external `href` on or inside a marker are still stripped, and `marker-*` still take only `none` or `url(#fragment)`. `tests/fixtures/svg/presentation-vectors.json` gains 8 attribute rows and 6 whole-document rows, which `SvgPresentationParityTest` runs through the full sanitiser. -- **The discovery-cache tests no longer fail under `--parallel`.** The services write through `cache()->store('file')`, and Testbench points that store at one directory under `vendor/`, shared by every test and every parallel worker. Two workers searching for the same term wrote the same key, so one read the other's result against its own empty database, and a `flush()` in one worker deleted directories another was scanning (`UnexpectedValueException`). `DiscoveryCacheInvalidationTest` or `DiscoveryActionsTest` failed in 6 of 10 parallel runs; with each test owning a temporary file-cache directory, 10 of 10 pass. Test-only: no production code changed. `tests/Feature/FileCacheIsolationTest.php`. - -### Security +- **An icon's subcategory, `sub`.** `IconBrowserService::getIcons()` loads each term's `parent_id`, which tells a nested category from a top-level one, and the new `Icon::$primary_subcategory` returns the nested category the icon sits in (null at the top level), reading the loaded terms when they carry `parent_id` and querying otherwise. `transformIcon()` adds `sub`, the subcategory's slug; every other key is unchanged. This is the per-icon counterpart of the `subs[]` filter. `tests/Feature/IconSubcategoryTest.php` (all 4 cases fail on the previous code). -- **Conflict floors for four Symfony components** (`composer audit`, 2026-11-05). `symfony/http-foundation`, `http-kernel`, `routing` and `yaml` below 7.4.23, or from 8.0 below 8.1.11, carry seven advisories (among them CVE-2026-118514, HEAD requests bypassing `access_control` methods, and CVE-2026-118516, host placeholders matching the start of the path; PKSA-nktx-f1n1-b4yd, YAML parse-time blow-up). `composer.json` now declares them as `conflict`, which Composer also applies when core is a dependency, so a pack or application installing core cannot resolve a vulnerable release. -- **The CSS `url()` guard can no longer be bypassed** (S1, 2026-11-04 security audit). The guard matched the literal characters `url(`. So a CSS escape (`\75rl(`, `\000075rl(`, `u\rl(`), a comment inside the token (`u/**/rl(`), or a fetching function with no `url(` in it (`image-set(`, `-webkit-image-set(`, `image(`, `src(`, `cross-fade(`, `element(`) kept a style that made the viewer's browser fetch an attacker's URL. A new `Support\SvgCssGuard` tokenises a value as a browser does, then allow-lists it: `url()` survives only for a same-document fragment, other functions only from a short list of inert ones (colour, `var`, maths, transform, easing), and no at-rule survives. The check covers the `style` attribute, `