feat(wasm): граница конфига — loadConfig, отпечаток, rgba-эмиссия, TS-типы (PR-b) - #122
Conversation
Две копии enum-а одного понятия (ядро и WASM-граница, 31+ ссылка) расходились бы молча — у словаря темы теперь один дом: labcolors_core::Theme несёт kebab-контракт parse/key и карту условий просмотра (та же карта surround-ов, что у контекста чистоты, с дефолтным Yb — на границе фон ещё неизвестен). Граница оставляет себе только обёртку ошибки (parse_theme → BindingError::UnknownTheme); viewing_conditions стал безошибочным — все четыре темы полностью откалиброваны, Result был архитектурной страховкой второй копии. Варианты LightIncreasedContrast/DarkIncreasedContrast → канонические LightIc/DarkIc.
…ba-эмиссия JSON-контракт конфига живёт ТОЛЬКО на границе (serde в wasm-крейте; ядро — ноль runtime-зависимостей): ConfigDto зеркалит ThemeConfig 1:1, enum-меню — tagged kind/kebab-строки, конверсия в обе стороны с честным Err на несериализуемом варианте non_exhaustive-меню. Отпечаток конфига — FNV-1a 64 над канонической JSON-сериализацией DTO (нормализация парсингом: пробелы и порядок ключей не влияют) — третий компонент ключа контракт-кэша: два конфига не делят записи. loadConfig наружу отдаёт отпечаток hex-строкой (u64 не влезает в JS number без потерь). Загрузка атомарна: невалидный конфиг (парсинг, неизвестная позиция меню, полный preflight ядра validate=компиляция) отклоняется структурной ошибкой invalid_config и не трогает состояние. После загрузки resolveTheme (сигнатура неизменна) эмитит string-keyed контракт конфига; полы ролей предвычислены на загрузке (свойство контракта, алиас несёт пол цели). RoleSpec::legal_floor — одна семантика пола для обеих таблиц (Role-keyed и string-keyed). rgba-граница: Resolved::Rgba теперь реальный исход RoleOutcome::Rgba (tintHex/alpha/compositeHex/Lc/WCAG + готовая css-строка rgb(R G B / A) — формат стаба labui; vars несёт её же: браузер композитит на живой подложке).
Три инварианта: (1) загрузка переключает контракт на string-keyed и разводит кэш-пространства — тот же (bg, тема) сразу после смены конфига обязан отдать НОВОЕ пространство ключей (попадание в чужую запись = кэш-коллизия = чужие цвета); (2) паритет: движок эмитит байт-в-байт то же, что прямой resolve_named_set той же таблицы — граница ничего не подменяет; (3) атомарность отказа: битый JSON и неизвестная позиция меню (ошибка называет позицию) не меняют состояние. Второй конфиг (acme) — минимальный фиолетовый бренд без палитры: пустые сентименты легальны (порог разделения 0).
TS-контракт в typescript_custom_section (единственный дом типов пакета): ThemeConfig (вход loadConfig — JSON.stringify типизированного объекта), RgbaRole в объединении RoleResult (vars несёт готовую css-строку rgba). Headless-смоук в живом wasm-рантайме: два конфига → разные отпечатки, разные эмиссии одной роли, пространство ключей конфига; rgba-роль доходит до JS с css; битый JSON — структурная ошибка invalid_config.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (11)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
…surround 2 major одного класса: 64-битный отпечаток различает конфиги ВЕРОЯТНОСТНО, а кэш-коллизия = чужие цвета. Несущая гарантия теперь — очистка кэша при загрузке конфига (одномоментно в кэше ровно одно пространство ключей); отпечаток остаётся идентичностью конфига наружу и belt-and-suspenders в ключе, его дока переформулирована честно. Тест фиксирует пустой кэш после загрузки. Отклонено с основанием: валидация α на границе — недоменная α режется полным preflight-ом ядра (validate = компиляция, границы с is_finite); доказано регресс-тестом через loadConfig (ошибка называет ручку). Карта тема → surround сведена в ОДНУ (Theme::vc_by): вторая копия в vc_for_context расходилась бы тихо при добавлении темы — тот же класс, что дублированные литералы домена. Паритет-тест доведён до всех полей границы (lc/wcag/compressed/floor_override/alpha/composite_*); headless-ассерт пространства ключей строгий (ровно контракт конфига); TS-док vars больше не обещает только hex.
Изолированный верификатор вскрыл выживший мутант: legal_floor конфиг-ролей
терялся бы на named-пути незаметно — ни один тест его не сравнивал. Хуже:
алиасы вообще не доходили до контракта границы (--lab-{alias} у потребителя
не существовал бы) — ядро их не резолвит (алиас — не рецепт), теперь граница
эмитит исход ЦЕЛИ под именем алиаса. Паритет-тест получил оракул пола (та же
семантика, что у загрузки: спека роли, алиас наследует цель) + значенческие
гарды не-вакуумности (label-primary = 4.5, счётчик якорных ролей); тест
кэш-пространств проверяет наследование пола алиасом через acme (btn-label →
body-text → 4.5). Точечный RED-proof: ровно тот выживший мутант
(map_resolved(_, None) на named-пути) теперь падает паритетом.
…амок README (#136) * docs(readme): синхронизация с фактическим кодом ядра Секция ролей устарела полностью: реально 20 ролей (Role::ALL), имена label-* (HIG-переименование из text-*), Border/Shadow разветвлены, добавлено семейство Fill, роль Surface удалена, дефолтный подтон — RoleChroma::Curve (тонированный), а не ChromaPolicy::Neutral (другой enum решателя). Таблица переписана построчно по RoleTable::default(). - пример API семантики: Role::TextPrimary → Role::LabelPrimary, реальные значения (#0A0A10, lc 102.7) посчитаны кодом; пример компилируется - API-пример и пример семантики закреплены doctest-замком в lib.rs (#[cfg(doctest)] include_str! README) — rust-блоки компилируются навсегда; иллюстративные блоки/формулы помечены text - «Структура проекта» дополнена реальными модулями (accent, alpha, chromafast, cleanliness, config, greyfast, ladder, lut, wcag, oklch, gamma_data) с назначениями из шапок - «Тесты»: полный набор CI-гейтов (clippy+fmt, test, audit, wasm+headless, npm typecheck/test) вместо заниженного - «Что дальше»: убран сделанный runtime-engine, surface-jnd переформулирован под реальность (роли surface нет, border-*/fill-* уже якорные) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(pkg): формат эмиссии — vars несут oklch, не hex После релиза 0.4.0 (#129/#130) CSS-переменные несут готовое oklch(L% C H [/ A]) (wasm/lib.rs oklch_css), а сырой #RRGGBB остаётся данными роли (roles.<ключ>.hex). Исправлены вводный абзац, пример vars (реальные oklch-строки) и описание vars/SolvedColor; добавлено поле css (готовое oklch(L% C H)) — сверено с TS-типами wasm/lib.rs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(adr): ADR-0001 принят и внедрён Статус «предложено» → «принято, внедрено» (config.rs — #121, wasm loadConfig — #122). Синхронизированы имена API: RoleTable::from_config → ThemeConfig::compile_named_role_table → NamedRoleTable, резолв — resolve_named_set. Клейм про отпечаток актуализирован: отпечаток загруженного конфига реально вычисляется (FNV-1a над DTO, config_dto.rs) и продевается в кэш-ключ через loadConfig; встроенная дефолт-таблица остаётся на DEFAULT_TABLE_FINGERPRINT = 0, корректность при смене конфига держит wholesale clear(). Историческая аргументация не тронута. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(core): актуализация комментариев по фактическому коду Только комментарии и doc-строки, поведение не тронуто. - semantic.rs: проза шапки модуля и doc-строк констант называла роли text-* как текущие — обновлено на label-* (упоминания «Was text-*» как история оставлены) - cache.rs: шапка и doc DEFAULT_TABLE_FINGERPRINT утверждали «отпечаток — константа-заглушка, пока жива только дефолт-таблица» — устарело: loadConfig влит, отпечаток конфига вычисляется в engine.rs и продевается в ключ, корректность держит wholesale clear() - dto.rs: набор ролей уже 20 — комментарий больше не подаёт «10 → 20» как будущее; issue #59 (акцентная лестница) — отдельное будущее изменение - benches/resolve_set.rs: хроматика больше не «падает в полный live-solver ~1 ms» — chromafast влит (memo), в steady state бенч меряет O(1)-путь ~0.8–1.2 µs; стейл «12 ролей» убран Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(pkg): TranslucentRole в union + справочник loadConfig/recheckContrast/muddiness/confidence H1: union RoleResult потерял TranslucentRole — реально 4 состояния (lib.rs:100). Добавлено описание TranslucentRole (поля из TS-секции lib.rs:82-98) и «одно из четырёх состояний». L2: справочник API дополнен методами движка, сверенными с TS-секцией lib.rs — loadConfig(json)→16-hex отпечаток (главная фича конфиг-границы), recheckContrast(bgHex,fgHexes,theme)→Float64Array (примитив adaptTheme, adapt-theme.d.ts:7), muddiness(hex)→number, confidence(hex)→number. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(readme): фиксы по фактическим значениям и структуре гейтов - M1: комментарий sample_hex(13) — реальный вывод, пересчитан кодом (["#FFFFFF", "#F8F8FA", "#E7E8EC", ..., "#101012"]) - M2: mermaid dark #101012 — J' ≈ 9.1 (light.at(1.0).jp = 9.05), было ≈ 4 - L1: «пять гейтов» → четыре CI-джоба (lint/test/audit/wasm); npm typecheck/ test — блокирующий шаг внутри wasm-джоба (ci.yml) - M6: «акцентная лестница как рецепт (#59)» — рецепт ladder влит и поглотил GAP #59 (config.rs:26); открытое = PR-c (ломающая чистка ядра) + поезд labui - заметки: снята временная формулировка «сегодня они держат»; карта помечена как продакшн-модули (тест-модули golden_tests.rs/config/tests.rs вне карты) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(adr): сняты внутренние противоречия под статусом «внедрено» M3: клеймы, устаревшие после мержа #121/#122: - S_PERC_MIN уже пересчитывается из конфиг-якорей (тест s_perc_min_recomputed_from_config_anchors_matches_frozen), не «БУДЕТ» - ladder влит (RoleSpec::Ladder, crate::ladder) и поглотил GAP #59 — больше не «единственный рецепт без прототипа» - IC_DECORATIVE_FLOOR_MIN конфигом НЕ экспонирован (сверено с TS ThemeConfig) — убран из списка экспонируемых ручек, помечен как физика ядра - «Путь миграции»: PR-a/PR-b помечены [сделано, #121/#122], поезд/PR-c [открыто]; имена API в выполненных шагах синхронизированы M4: эмиссия oklch, не rgba — движок эмитит oklch(L% C H / α); несуществующий Resolved::Rgba → Resolved::Translucent (semantic.rs:1122). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(wasm): свип rgba→oklch на границе эмиссии + четыре исхода Только комментарии/doc-строки, поведение не тронуто. WASM — граница CSS- эмиссии, где заявляется эмитируемая форма: - M4: dto.rs, engine.rs, lib.rs — «emission is rgba(tint, alpha)» / «наружу уходит rgba» / «rgba-роли лестницы» → oklch(L% C H / A); поле tint_hex «CSS rgba() carries» → «oklch(… / A) carries» - M5: dto.rs шапка — «#59 accent ladder (a new resolved shape)» противоречил RoleOutcome::Translucent, уже присутствующему ниже; переформулировано: набор уже 20, лестница #59 добавила Translucent, дальнейшие исходы проходят так же - dto.rs: «three honest outcomes» → four (RoleOutcome несёт 4 варианта: Color/None/Translucent/Unreachable) Ядро (semantic.rs/config.rs) не тронуто: там «rgba» — внутреннее имя пути резолва (согласовано с функциями resolve_rgba_*), а не CSS-клейм. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(adr): нормативные секции сверены с кодом (раунд-3, потребительский FAIL) Автор конфига больше не вводится в заблуждение — каждый клейм по коду: - Приложение A: таблица позиций 15 → 25 (LadderPosition::ALL, ladder.rs:214-240); добавлены NeutralFill×4, NeutralBorder×2, Shadow×4 с точными пер-темными α (alpha_pair, ladder.rs:277-291) - «SkeletonBase — единственная пер-темная альфа» неверно: пер-темные несут скелетон + нейтральная рампа + тени (11 позиций); переписано - brand.anchor «один hex» → пер-темная четвёрка ThemeAnchors {light,dark, light_ic,dark_ic} (config.rs:295; согласовано с :190) - VC-пресеты snake «srgb_ic|dim_ic» → kebab «srgb-ic|dim-ic» (config_dto.rs:92; snake давал бы invalid_config) - меню рецептов 5 → 6: добавлен decorative-lc (config.rs:446); kinds — kebab - neutral(pick) 3 → 5: добавлены Edge, Inverted (config.rs:508) - fx-focus-ring-neutral/fx-glow-inverted заземлены пиками Edge/Inverted (config/tests.rs:1237-1242) - тени: «переход на альфа-якоря вне поезда» устарело — уже полупрозрачные ladder-позиции (config/tests.rs:1286-1295) - двойной enum тем «унифицируется» → унифицирован (theme.rs:15) - golden semantic.rs:3352-3655 → тест resolve_set_golden_hex_is_byte_for_byte_stable, const GOLDEN ≈3785 - Значенческая сверка: «rgba(тинт, α)» — внутренний формат core-теста против rgb-стаба, НЕ CSS-эмиссия (та oklch); уточнено Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs: дешёвые фиксы читаемости README и пакета (раунд-3) README: - UCS-формулы: реализация lcs.rs → формулы в spaces/cam16.rs (ucs_j/ucs_m), lcs.rs их применяет - сквозная нумерация: сняты орфанные «2.»/«3.» с ### Кривая/### Контраст (перед ними шёл ### ViewingConditions без номера) - глоссы для дизайнера при первом употреблении: JND (минимально заметная глазу разница), C1-непрерывность (гладкая, без изломов производной) packages/colors: - убрана колонка brotli из таблицы размеров (CI меряет только raw+gzip, ci.yml:245-252), добавлена честная строка провенанса - добавлена опция root в WatchThemeOptions (watch-theme.d.ts:23, Node) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM * docs(adr): точный указатель ThemeAnchors — определён в ladder.rs Финдинг закрывающего верификатора раунда-3 (recommended minor): тип определён в ladder.rs:56, config.rs лишь использует его полем Brand.anchors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Y8JyGJvXX872YqYrGa1xM --------- Co-authored-by: Claude Code <daniilerosov12@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Что это
Мост конфига в браузер: движок, научившийся принимать конфиг клиента (#121), теперь принимает его ЧЕРЕЗ WASM-границу.
loadConfig(json): полный preflight ядра (validate = компиляция), атомарный отказ (invalid_config, состояние не меняется), возвращает отпечаток конфига (16 hex).resolveThemeсигнатурно неизменен; после загрузки эмитит string-keyed контракт конфига.Resolved::Rgba→RoleOutcome::RgbactintHex/alpha/compositeHex/Lc/WCAGи готовой css-строкойrgb(R G B / A)(формат стаба labui);varsнесёт её же.Theme: ядро несёт kebab-контракт и карту VC; граница — только обёртку ошибки (снята вторая копия enum-а, 31 ссылка).typescript_custom_section(единственный дом типов пакета).Тесты
resolve_named_set; атомарность отказа (ошибка называет позицию меню).