Skip to content

feat(wasm): граница конфига — loadConfig, отпечаток, rgba-эмиссия, TS-типы (PR-b) - #122

Merged
lemone112 merged 6 commits into
mainfrom
feat/wasm-config-sdk
Jul 2, 2026
Merged

feat(wasm): граница конфига — loadConfig, отпечаток, rgba-эмиссия, TS-типы (PR-b)#122
lemone112 merged 6 commits into
mainfrom
feat/wasm-config-sdk

Conversation

@lemone112

Copy link
Copy Markdown
Collaborator

Что это

Мост конфига в браузер: движок, научившийся принимать конфиг клиента (#121), теперь принимает его ЧЕРЕЗ WASM-границу.

  • loadConfig(json): полный preflight ядра (validate = компиляция), атомарный отказ (invalid_config, состояние не меняется), возвращает отпечаток конфига (16 hex).
  • Отпечаток — FNV-1a 64 над канонической JSON-сериализацией DTO: третий компонент ключа контракт-кэша, два конфига не делят записи (тест кэш-коллизии).
  • resolveTheme сигнатурно неизменен; после загрузки эмитит string-keyed контракт конфига.
  • rgba-граница: Resolved::RgbaRoleOutcome::Rgba c tintHex/alpha/compositeHex/Lc/WCAG и готовой css-строкой rgb(R G B / A) (формат стаба labui); vars несёт её же.
  • Один канонический Theme: ядро несёт kebab-контракт и карту VC; граница — только обёртку ошибки (снята вторая копия enum-а, 31 ссылка).
  • TS-типы конфига и rgba-роли — в typescript_custom_section (единственный дом типов пакета).
  • serde/serde_json — только в wasm-крейте (ядро — ноль runtime-зависимостей); size-гейт CI информационный, до/после в логе шага bundle size.

Тесты

  • Движок: смена контракта + развод кэш-пространств; байт-в-байт паритет с resolve_named_set; атомарность отказа (ошибка называет позицию меню).
  • DTO: labui-эталон через JSON туда-обратно без потерь (PartialEq ядра) + компиляция; детерминизм/различимость отпечатка.
  • Headless (wasm-рантайм): два конфига → разные отпечатки/эмиссии/пространства ключей; rgba доходит до JS.

Daniel from Labpics added 4 commits July 2, 2026 12:46
Две копии 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.
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 41af7ab5-6a48-4ca4-8640-a9d8c348b549

📥 Commits

Reviewing files that changed from the base of the PR and between 1b428d5 and 83fdd90.

📒 Files selected for processing (11)
  • crates/labcolors-core/src/cleanliness.rs
  • crates/labcolors-core/src/semantic.rs
  • crates/labcolors-wasm/Cargo.toml
  • crates/labcolors-wasm/src/cache.rs
  • crates/labcolors-wasm/src/config_dto.rs
  • crates/labcolors-wasm/src/dto.rs
  • crates/labcolors-wasm/src/engine.rs
  • crates/labcolors-wasm/src/error.rs
  • crates/labcolors-wasm/src/lib.rs
  • crates/labcolors-wasm/src/theme.rs
  • crates/labcolors-wasm/tests/wasm_parity.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wasm-config-sdk

Comment @coderabbitai help to get the list of available commands.

Daniel from Labpics added 2 commits July 2, 2026 13:16
…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-пути) теперь падает паритетом.
@lemone112
lemone112 merged commit 95f0fc2 into main Jul 2, 2026
5 checks passed
@lemone112
lemone112 deleted the feat/wasm-config-sdk branch July 2, 2026 10:42
lemone112 added a commit that referenced this pull request Jul 3, 2026
…амок 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant