Skip to content

feat: typography tokens editor and ds-service typography style operations - #121

Merged
vkaltyrin merged 27 commits into
devfrom
feature/typography-tokens-editor
Oct 27, 2026
Merged

vkaltyrin merged 27 commits into
devfrom
feature/typography-tokens-editor

Conversation

@vkaltyrin

@vkaltyrin vkaltyrin commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Что это

OpenSpec change add-typography-tokens-editor и его реализация. Change одобрен на ревью в этом PR, реализация перенесена сюда из #125.

Раздел «Типографика» переводится на прототип SDDS Portal и на операции ds-service, как «Палитра» и «Цвета».

База — dev, в котором уже есть #109 (версии, журнал и многопользовательское редактирование), #123, #132, импорт и экспорт дизайн-систем и удаление admin и db-service. Спеки и код приведены к модели #109 отдельными коммитами; ранние коммиты на схеме #109 по отдельности не собираются.

Ключевые решения change

  • Стиль — три токена. screen-l.body.m.normal|medium|bold отличаются только весом. В интерфейсе это один стиль: под семейством выбирается начертание Regular, Medium или Bold, а ниже — его вес.
  • Области записи:
    • размер, высота строки и трекинг пишутся в три токена стиля на выбранном экране;
    • вес начертания и семейство пишутся на всех экранах: в данных они одинаковы на трёх экранах.
  • Одно значение в px. ds-service переводит px и em в формат каждой платформы. На вебе rem при базе 16. На iOS вес словом: 100 thin, 200 ultraLight.
  • Свои значения iOS и Android. По умолчанию значения платформ строятся из веба. У веса, размера, высоты строки и трекинга «+» задаёт платформе своё значение, «×» снимает его, как у цветов в feat(ds): градиент как вид значения цветового токена #122. Признак своего значения — отличие от веба: для типографии перевод точный, поэтому миграция не нужна. Отвергнут вариант с колонкой overridden, как в feat(ds): градиент как вид значения цветового токена #122; если feat(ds): градиент как вид значения цветового токена #122 введёт её для всех значений, типографию стоит перевести на неё.
  • Семейство выбирается из семейств дизайн-системы.
  • Состав стилей берётся из токенов темы.
  • «Влияние» — настоящие компоненты из конфигураций.

ds-service

  • GET /api/ds/tenants/{tenantId}/typography-styles (с необязательным versionId) отдаёт:
    • стили screen-l, screen-m, screen-s в px с токенами начертаний;
    • семейства шрифтов;
    • число тем;
    • компоненты, которые используют стиль;
    • технические редакции значений начертаний (valueRevisions) вместо общего editRevision;
    • свои значения платформ (overrides): по iOS и Android только поля, которые отличаются от веба.
  • Чтение идёт на границе черновика или опубликованной версии; при versionId canEdit=false, недоступная версия — 404.
  • Использование считается по последним редакциям значений свойств вариаций и инвариантов компонентов на той же границе. Ссылка на стиль бывает трёх видов: без экрана (body.m.normal), с экраном и через token_id. Ссылки через token_id отбираются только на токены типографии.
  • PATCH /api/ds/tenants/{tenantId}/typography-styles/{tokenId} пишет предметные редакции изменённых значений в текущую тему одной транзакцией:
    • базовая редакция — baseRevisions по значению или X-DS-Base-Revision;
    • при правке одного значения побеждает последняя запись, путь попадает в warnings заголовка X-DS-Mutation-Metadata; 409 TENANT_EDIT_CONFLICT нет;
    • мутация с versionId — 400 historical_version_read_only;
    • правки видны в журнале изменений и отменяются им;
    • с platform: "ios" | "android" размеры и веса пишутся только в эту платформу; без platform — в веб и в платформы, у которых поле не своё. Семейство общее для платформ, platform с fontFamily — 400;
    • использование в компонентах не пересчитывается: от правки оно не меняется, components в ответе пустой.
  • Остальные поля значения сохраняются. Пишутся только платформы, у которых уже есть значение. Если у токенов стиля, которых касается правка, в теме нет значений, ответ — 409 TYPOGRAPHY_VALUES_MISSING вместо пустого 200.
  • Проверка значений использует тексты прототипа. Вес допускается от 100 до 900 с шагом 100. Ошибка — 400 INVALID_TYPOGRAPHY_VALUE, поле передаётся в details.field.
  • Операции описаны в route-manifest.json и OpenAPI.

Клиент

  • Модуль modules/typographyTokens:
    • домен;
    • порт и HTTP-адаптер;
    • очередь автосохранения на subjectMutation с базовыми редакциями из subjectRevisions.
  • Экран pages/typography:
    • переключатель «Крупные / Средние / Мелкие»;
    • дерево с поиском и сворачиванием групп;
    • борд-шкала;
    • инспектор с разделами «Стиль» и «Влияние». В «Стиле» по порядку: семейство, начертание, вес выбранного начертания, размер и высота строки, трекинг;
    • начертание и вес выбираются в своём меню, как семейство: нативный <select> браузеры рисуют по-разному;
    • образцы на борде рисуются весом начертания, выбранного в инспекторе;
    • у веса, размера, высоты строки и трекинга «+» открывает выбор iOS или Android, под полем появляется строка «↳ iOS» / «↳ Android» со своим значением и «×».
  • Сохранение по модели feat(ds): версии, журнал изменений и версионная палитра #109:
    • одна операция на стиль после паузы 400 мс;
    • своего статуса сохранения у раздела нет: состояние показывает общий индикатор черновика в шапке редактора;
    • ошибка сохранения показывается баннером в инспекторе с «Повторить», в том числе 409 TYPOGRAPHY_VALUES_MISSING;
    • автоповтор после ошибки сети или 5xx (1 с с удвоением до 30 с), «Повторить» отправляет сразу;
    • состояние очереди сообщается редактору (setDraftAutosaveActivity), стили перечитываются по SSE после очереди;
    • отправка очереди при уходе со страницы.
  • Опубликованная версия (?versionId) открывается только для чтения: все поля, включая выбор начертания, недоступны и приглушены; переход из журнала по ?tokenId выбирает стиль.
  • Очередь живёт в сессии темы и переживает перемонтирование экрана.
  • Ошибка значения показывается у поля, у стиля остаётся последнее сохранённое значение.
  • Баннера прежнего черновика нет: неподтверждённые записи отправляет сессия редактора feat(ds): версии, журнал изменений и версионная палитра #109.
  • Стили — правила прототипа под корнем .typography-screen, как color-tokens.css.
  • Удалены прежний экран, действия добавления и отключения токенов типографии и TypographyPicker.

Расхождения с прототипом

Полная таблица с причинами — в design.md. Коротко:

  • выбор начертания и поле «Вес» для него вместо одного «Веса»: у стиля три токена;
  • выбор семейства из дизайн-системы вместо «одно на тему + Custom»;
  • нет фильтра проблемных стилей и отметок изменений;
  • нет кнопки сброса стиля;
  • ошибка значения показывается у поля;
  • «Токены» убраны из «Влияния»;
  • на холсте только борд;
  • исправлено меню семейства: в прототипе оно обрезается полем;
  • борд растянут на ширину холста. В прототипе он фиксирован под 1440 px, и на широком экране справа остаётся пусто;
  • образец на борде — одна строка высотой в высоту строки стиля и обрезается по ней, поэтому уменьшение высоты строки видно. Строка борда растёт, если высота строки больше образца прототипа;
  • колонка имени шрифта на борде шире (104 px вместо 64), «SB Sans Display» видно целиком, длинное имя переносится;
  • свои значения iOS и Android: «+» у поля и строки «↳ iOS» / «↳ Android» с «×»;
  • содержимое строки дерева отступает от скруглённых краёв выделения на 8 px;
  • шеврон группы в дереве нарисован CSS, как в «Палитре» и «Цветах»: символы «⌄» и «›» шрифт ставит ниже текста;
  • образцы на борде рисуются весом выбранного начертания (решение дизайнера);
  • выделение строки борда — по макету Figma, как в «Палитре» и «Цветах»: строка не заливается, образец — карточка с фоном, серая рамка при наведении и синяя у выбранного стиля (решение дизайнера).

Проверки

  • ds-service.

    • Тесты домена и use case.
    • HTTP-тест на Testcontainers. Он покрывает:
      • области записи;
      • другие темы;
      • использование через token_id, имя с экраном и инварианты;
      • компоненты другой дизайн-системы;
      • коды ошибок и права.
  • После rebase на текущий dev и правок по QA:

    • tools/verify full --change add-typography-tokens-editor — PASS: openspec validate, ./gradlew verifyFull, strictacode бэкенда, npm run verify:full и strictacode JS после пересчёта базовой линии. Красных тестов манифеста db-service больше нет: db-service удалён из dev.
    • После правок по QA: тесты клиента (824 в 103 файлах), tsc, eslint, strictacode javascript-client и openspec validate — PASS.
    • На стенде проверены шеврон дерева, меню начертания и веса, вес образцов по выбранному начертанию, карточки образцов и выделение строк борда. Режим просмотра покрыт тестом экрана: опубликованной версии на стенде нет.
  • После rebase на dev с feat(ds): версии, журнал изменений и версионная палитра #109 и feat(ds-service): Add component editor model API #123:

    • tools/verify full прошёл, кроме двух тестов манифеста маршрутов db-service про appearance-editor: они были красными и на чистом dev.
    • HTTP-тест типографики переведён на IsolatedPostgresDatabase: в dev компоненты сидятся при создании дизайн-системы, поэтому каталог загружается до неё.
  • До rebase на dev, на feature/ds-versions, после правок по ревью:

    • tools/verify full --change add-typography-tokens-editor — PASS, включая strictacode.
    • ./gradlew build --continue в ds-service — PASS (detekt, spotless, тесты). HTTP-тест дополнительно покрывает редакции значений в чтении, устаревшую базу с warnings:["/value"], обрезку метаданных, публикацию (версия показывает прежние значения, запись в неё — 400), журнал и откат.
    • Клиент: npm run build, npm test (689 тестов в 89 файлах) — PASS; npm run lint — 24 ошибки, все в файлах вне PR, столько же на чистом feature/ds-versions.
    • openspec validate change и --specs — PASS на 1.6.0.
  • Базовая линия strictacode javascript-client пересчитана от dev по решению разработчика: сложность 4935, плотность 9.58 (в dev — 4740 и 9.41).

  • До rebase на feat(ds): версии, журнал изменений и версионная палитра #109: тесты домена, очереди, адаптера, переноса черновика, борда и экрана; tools/verify full — PASS.

  • На локальном контуре с копией рабочей базы (base_default):

    • размер Body M на «Крупных» меняет три токена screen-l.body.m.* на трёх платформах, screen-m и screen-s не меняются;
    • вес Bold и семейство меняются на всех экранах, другие темы не меняются;
    • у Body M 34 компонента и 67 использований — совпадает с выборкой из конфигураций;
    • вёрстка сверена с прототипом;
    • после rebase экран проверен на контуре feat(ds): версии, журнал изменений и версионная палитра #109 (отдельный ds-service из ветки на копии базы): правки начертания, веса, размера и высоты строки сохраняются, образец обрезается по высоте строки;
    • свои значения: iOS 17 у Body M сохраняется только в iOS; после правки веба на 18 iOS остаётся 17, Android становится 18; «×» снимает своё значение;
    • запрос использования на копии базы — 40–50 мс вместо 140.
  • Не выполнено:

    Change будет заархивирован после этих проверок.

@coderabbitai

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 0d571aa9-2f4e-402a-9c10-c5fc4662a617
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@vkaltyrin vkaltyrin self-assigned this Oct 19, 2026
@vkaltyrin vkaltyrin changed the title docs(openspec): propose typography tokens editor feat: typography tokens editor and ds-service typography style operations Oct 20, 2026
@vkaltyrin
vkaltyrin changed the base branch from dev to feature/ds-versions October 21, 2026 09:48
@vkaltyrin
vkaltyrin changed the base branch from feature/ds-versions to dev October 21, 2026 09:48
@vkaltyrin
vkaltyrin force-pushed the feature/typography-tokens-editor branch from 0363097 to ef0e848 Compare October 21, 2026 09:49
@vkaltyrin
vkaltyrin changed the base branch from dev to feature/ds-versions October 21, 2026 09:49
/** Поля редакции значения свойства, которые нужны запросу использования. */
const val VALUE_FIELDS = "latest.token_id, latest.value"
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UUID подставляется в SQL строкой. Инъекции нет, потому что значение типизировано, но это расходится с остальным кодом на Exposed. Кроме того, условие v.token_id IS NOT NULL захватывает все токен-ссылки, включая цвета, а отсекает их только LEFT JOIN. Каждый GET и PATCH проходит по всем значениям свойств дизайн-системы.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено: ссылки через token_id отбираются только на токены типографии, PATCH больше не пересчитывает использование. UUID передаётся параметром с #109.

.filter { (platform, value) -> value != token.values[platform] }
return values.takeIf { it.isNotEmpty() }?.let { WrittenTypographyToken(token.id, token.name.toString(), it) }
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

пишет только в платформы, у которых в теме уже есть значение. Если у темы нет значений типографии, ревизия повысится, а записано ничего не будет

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Исправлено: если у темы нет значений стиля, PATCH отвечает 409 TYPOGRAPHY_VALUES_MISSING вместо пустого 200. Ревизия при пустой записи и раньше не росла: с #109 редакция создаётся только при записи значения.

retry: () => session.autosave.retry(),
fieldError,
reload: () => load(session),
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

При конфликте 409 клиент перечитывает стили, но саму модель темы (live.theme) не обновляет. Другие разделы до перезагрузки могут показывать старую типографику

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

После rebase на #109 конфликта 409 у типографии нет: при правке одного значения побеждает последняя запись. Модель темы обновляется из ответа PATCH, а после чужой правки — перечитыванием по SSE.

@malilex
malilex force-pushed the feature/ds-versions branch from 74386d7 to d143d81 Compare October 22, 2026 12:26
@vkaltyrin
vkaltyrin force-pushed the feature/typography-tokens-editor branch from fac88ef to b8dd05f Compare October 22, 2026 13:50
@vkaltyrin
vkaltyrin changed the base branch from feature/ds-versions to dev October 22, 2026 13:50
Typography section by the SDDS Portal prototype: screen sizes, style tree,
scale board and style inspector with autosave, plus ds-service operations
that read typography styles in px with component usage and update a style
across its tokens and platforms.
Keep unsaved draft records on save errors, restrict font weight to steps of 100
and map iOS 100/200 to thin/ultraLight as the existing token parser does.
Read and edit typography styles of a theme (OpenSpec change
add-typography-tokens-editor):

- GET /tenants/{tenantId}/typography-styles returns the styles of
  screen-l, screen-m and screen-s in px with variant tokens, the font
  families of the design system and the components that use each style;
- PATCH /tenants/{tenantId}/typography-styles/{tokenId} writes size, line
  height and letter spacing to the three style tokens of the token screen,
  variant weights and the font family to the style on every screen, in the
  platform formats of web, android and ios, and checks and increments the
  shared theme editRevision.

Values are validated with the prototype messages; weights are limited to
100..900 in steps of 100 and map to iOS words as the token parser does.
Replace the old typography screen with the prototype Typography screen:
screen switch, style tree, scale board and inspector with style, variant
weights and component usage. Edits are saved by ds-service style
operations with the colors autosave queue; typography entries of the old
draft can be saved or discarded. The old screen, its add and disable
actions and TypographyPicker are removed.
Mark the implemented tasks, name details.field as the location of the
invalid field in INVALID_TYPOGRAPHY_VALUE and list the save status in the
toolbar among the prototype differences.
Board rows keep the prototype heights, which are tuned for the default
theme values, and grow when the letters of the sample (0.75 of the font
size plus 8 px) do not fit, so an enlarged style is not cropped.
The prototype board is fixed to the 1440 px window (719 px) and leaves an
empty area on wide screens; the board now takes the canvas width and the
sample column grows. The single-line sample uses a line height not smaller
than the font size, so a style with a short line height is not cropped.
Rebase the typography change onto the versioned multi-user editing model.
Style changes write subject revisions of token values with per-value base
revisions, report concurrent writes of /value in X-DS-Mutation-Metadata
warnings and never answer 409; the theme edit revision is gone from the
read and change contracts. Reads take versionId, return technical value
revisions and resolve component usage on the same boundary; writes to a
published version answer historical_version_read_only.

The editor wraps mutations in subjectMutation, takes base revisions from
subjectRevisions, reports its queue as the typography autosave source,
retries network errors with a growing delay, rereads styles after a remote
draft change once local queues are confirmed, opens a style from the
journal by tokenId and drops the legacy draft banner.
Read styles, families and values from token_revisions and
token_value_revisions on the transaction boundary with versionId support,
return the technical revision and stable id of every value, and resolve
component usage from the latest revisions of property values, styles and
tokens. A style change locks the design system and the style values,
rereads them and writes new draft revisions of changed values only, with
the /value path and the base revision from the body or X-DS-Base-Revision;
the response carries the mutation metadata headers. The theme edit
revision, its lock and TENANT_EDIT_CONFLICT are removed. OpenAPI refers to
the shared VersionId, OptionalBaseRevision and mutation metadata headers.
Typography mutations go through subjectMutation with base revisions of the
style values from subjectRevisions, the queue reports its state as the
typography autosave source and retries network and 5xx errors after 1 s
doubling up to 30 s. Styles are read with versionId, a published version
is read-only, a new theme model from the draft invalidation stream rereads
styles after local queues are confirmed, ?tokenId selects the screen and
style, and truncated metadata of typography-styles refreshes theme tokens.
The legacy draft banner is removed: ThemeEditorSession sends those entries.
…names in typography

Address the typography review:

- the inspector selects a variant (Regular, Medium or Bold) under the font
  family and shows one weight field for it, as the prototype has one weight;
- the board sample is one line of the style line height and is clipped by
  it, so reducing the line height is visible; the row grows when the line
  height is taller than the prototype sample;
- the font name column is wider and long names wrap instead of being cut.
The weight of the selected variant takes a full row under the variant
picker, like the variant field; size and line height share the next row
and letter spacing follows.
The editor header already shows the draft autosave state that the
typography queue reports to, so the section keeps no status of its own.
A save error is shown as an inspector banner with Retry.
The variant and weight fields sit in a grid with the same 4 px gap as the
font family block, so every field in the style section is 12 px apart.
…me values

- the usage query keeps only token references to typography tokens instead
  of every token_id value of the design system (440 of 2589 rows on a copy of
  the working database, 40-50 ms instead of 140 ms);
- PATCH no longer recomputes usage: it does not change with a style edit,
  and the response style has an empty components list;
- an edit of style tokens that have no values in the theme returns
  409 TYPOGRAPHY_VALUES_MISSING instead of 200 with nothing written.
The client keeps components of a style from the read when it applies an
edit response, and shows 409 TYPOGRAPHY_VALUES_MISSING as a save error with
Retry instead of a field error.
A platform field is its own when its value differs from web: the read
returns such fields in overrides, PATCH with platform ios or android
writes sizes and weights only to that platform, and a web edit no longer
overwrites own platform values. Writing the web value back removes the own
value. The font family stays shared across platforms.
A + on the weight, size, line height and letter spacing fields picks iOS or
Android and adds a '↳ iOS' row under the field; × removes it by writing the
web value. Also center the + on the field, keep the weight list chevron
aligned in override rows and give tree rows room inside the selection.
Record the own iOS and Android values decision and requirements, and
rebaseline javascript-client for them (density 9.26 -> 9.28 by the
developer's decision).
Use an SVG glyph instead of the font character, whose plus sits off the
line centre, so the sign is centred in its button.
…graphy design system

Since #109 landed in dev, component configurations are seeded when the design
system is created, so the catalog must be loaded first. The typography HTTP test
now uses IsolatedPostgresDatabase, whose schema is migrated before the catalog
is loaded.
…-only fields

- The group chevron in the typography tree is drawn with CSS like in Palette and
  Colors; the text glyphs sat below the label.
- Variant and weight lists use the same menu as the font family instead of a
  native select, whose popup each browser draws differently (light text on white
  in Chrome on Windows). Menu dismissal is shared by the three inspector menus.
- Disabled inspector fields are dimmed as a whole in read-only mode; only the
  family button was dimmed before. The variant stays available: it only picks
  which variant to show.
…ography editor

The branch is rebased onto dev after the design system import/export, the removal
of admin and db-service and #132. The javascript-client baseline is recomputed
from dev with the typography editor: complexity 4935 at density 9.58 over 51511
lines, status score 48.
@vkaltyrin
vkaltyrin force-pushed the feature/typography-tokens-editor branch from aaf78b0 to 82ce2d1 Compare October 26, 2026 11:42
…selection

- Board samples are drawn with the weight of the variant selected in the
  inspector; the variant choice moved to the screen and is shared by the
  inspector and the board.
- Board row selection follows the Figma layouts and the Colors swatch cards:
  no row fill or left bar, the sample gets a grey border on hover and a blue
  one when selected.
- The OpenSpec change records both designer decisions.
…y mode

- Board samples are cards with the Figma fill: the same translucent layer as
  #37393d over #212327, so the contrast with the board matches the layout.
- The variant list is disabled in read-only mode like every other field:
  nothing can be changed in a published version.
@vkaltyrin
vkaltyrin merged commit c115a4a into dev Oct 27, 2026
15 checks passed
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.

4 participants