Commit Graph

239 Commits

Author SHA1 Message Date
Александр Зимин b6ee703577 fix(auth): read roles из access_token, не id_token (sidebar reviews hidden bug) 2026-05-13 11:39:45 +00:00
Zimin A.N. d4ee30a7dd fix(webhook): scope_filter Hibernate ordinal bug + UI toggle для active
Два бага в /webhooks (user report):

1. **500 при выборе scope в форме**

POST /admin/webhooks/subscriptions с scopeFilter:["PUBLIC"] возвращал 500.

Root cause: WebhookSubscription.scopeFilter был List<DataScope> с
@JdbcTypeCode(SqlTypes.ARRAY). Hibernate сериализовал enum в TEXT[] как
ordinal (е.g. {0} для PUBLIC), а DB CHECK constraint
chk_webhook_scope_filter ожидает строковые имена. Insert падал
DataIntegrityViolationException → 500.

Fix: field теперь List<String> (raw enum names). Getter/setter
конвертируют в/из DataScope — API surface (DTO, service signatures,
WebhookDispatcher) не меняется.

2. **Нет toggle active/inactive в UI**

useUpdateWebhook hook был, но не использовался в UI. Status показывался
только read-only badge'ом. Невозможно деактивировать webhook через UI.

Fix: добавлен toggle button рядом с Status badge на /webhooks/:id.
Sends PUT с inverted active flag (backend treats PUT как full replace,
поэтому пересобираем full payload).

i18n: 'webhooks.action.activate' / 'deactivate' default labels (RU).
2026-05-13 14:02:35 +03:00
Zimin A.N. 5fb1658880 feat(sidebar): role-based visibility для /reviews
Phase 1d enhancement: 'Согласования' пункт сайдбара скрыт если у user'а
нет approver/reviewer ролей. Раньше показывался любому authenticated —
оператор без reviewer прав видел queue, кликал и получал пустую
страницу (или 403 если бы пробовал approve).

Visibility rules:
- 'Мои черновики' — auth (maker может быть любой)
- 'Согласования' — auth + (ordinis:schema:reviewer OR ordinis:record:reviewer
  OR ordinis:admin super-role) — иначе hidden
- 'Outbox' / 'Webhooks' / 'Аудит' — auth (admin-y но backend не gated
  отдельной ролью)

Новый NavItem.reviewerOnly flag — фильтр в visibleSections учитывает
оба flag'а (authRequired + reviewerOnly).

Применено и к desktop Sidebar и к MobileSidebar (DRY refactor — отдельный
task).
2026-05-13 13:52:40 +03:00
Zimin A.N. 36c06ae1da feat(auth): WorkflowRoles configurable claim path + ordinis:admin super-role
User report: token has roles в resource_access.ordinis.roles (client-scoped),
backend WorkflowRoles читал hardcoded realm_access.roles → forbidden_role
для всех reviewer actions.

Backend:
- WorkflowRoles: depends OrdinisAuthProperties → reads via rolesClaim path
  (же что ScopeContext). Default realm_access.roles, на staging/prod env
  var ORDINIS_AUTH_ROLES_CLAIM=resource_access.ordinis.roles (уже wired
  в helm helper).
- Nested claim path resolver — mirror ScopeContext.readClaimByPath
- New super-role ADMIN ('ordinis:admin') — holder автоматически проходит
  любой workflow check без явного назначения отдельных ролей. Convenience
  для small teams.

Frontend (usePermissions.ts):
- tokenRoles() читает оба claim path (realm_access.roles +
  resource_access.ordinis.roles) и объединяет — UI gate работает
  независимо от backend config
- hasRole() recognizes 'ordinis:admin' как super-role override
- ROLE_ADMIN exported

Docs:
- docs/operator/rbac.md: добавлен ordinis:admin row, JWT claim path note,
  Admin profile updated (super-role vs composite explanation)
2026-05-13 13:17:54 +03:00
Zimin A.N. c5b00ff623 refactor(auth): domain-specific role names (consistent convention)
User feedback: универсальный 'ordinis:approver' отступал от convention
'ordinis:<domain>:<action>'. Меняю на domain-specific:

WorkflowRoles constants:
- APPROVER → SCHEMA_REVIEWER ('ordinis:schema:reviewer')
- PUBLISHER → SCHEMA_PUBLISHER ('ordinis:schema:publisher')
- Новая RECORD_REVIEWER ('ordinis:record:reviewer')

Applied:
- SchemaDraftService.decide() → require SCHEMA_REVIEWER
- SchemaDraftService.publish() → require SCHEMA_PUBLISHER
- DraftService.approve() / .reject() → require RECORD_REVIEWER

Frontend usePermissions.ts:
- ROLE_APPROVER + ROLE_PUBLISHER → ROLE_SCHEMA_REVIEWER +
  ROLE_SCHEMA_PUBLISHER + ROLE_RECORD_REVIEWER
- Новый hook useCanReviewRecord() для record draft actions

Keycloak setup (updated):
- ordinis:schema:reviewer
- ordinis:schema:publisher
- ordinis:record:reviewer
(Composite role 'ordinis:admin' = all три + scope roles — для удобства.)
2026-05-13 12:44:23 +03:00
Zimin A.N. 24aa94d130 feat(auth): Phase 1d — backend role enforcement для workflow
User explicitly requested 'Phase 1d пора'. До этого MR'а backend гейтил
только maker-checker (self_approve_forbidden), frontend hooks возвращали
auth.isAuthenticated.

Реализация:

Backend WorkflowRoles (ordinis-rest-api/auth/WorkflowRoles.java):
- Component reads realm_access.roles из JWT claim
- Constants: ordinis:approver (decide/approve/reject), ordinis:publisher (publish)
- requireRole(role) throws 403 forbidden_role если missing
- Defensive parse: malformed/missing realm_access → empty roles
- 9 unit tests cover authenticated + anonymous + malformed JWT

Applied gates:
- SchemaDraftService.decide() — require ordinis:approver
- SchemaDraftService.publish() — require ordinis:publisher
- DraftService.approve() — require ordinis:approver
- DraftService.reject() — require ordinis:approver

WorkflowRoles is Optional в test ctors → unit tests без auth setup
не ломаются. Все existing tests должны проходить (legacy ctor overloads
сохранены).

Frontend usePermissions.ts:
- Stub return auth.isAuthenticated заменён на real role decode из
  auth.user.profile.realm_access.roles
- ROLE_APPROVER + ROLE_PUBLISHER constants exported
- Defensive parsing — graceful с missing/malformed claims
- useCanCreateSchemaDraft остаётся permissive (любой auth — maker'ом
  может быть anyone, scope ACL заведует видимостью)

Naming convention — colon-separated (consistent с ordinis:client:*
scope ролями), aligned с MR !161.

MIGRATION (BREAKING без правильной Keycloak setup):
1. Keycloak realm 'nstart' → Realm Roles → Create:
   - ordinis:approver
   - ordinis:publisher
2. Users → assign roles:
   - reviewer/approver users → ordinis:approver
   - publisher users → ordinis:approver + ordinis:publisher
3. После refresh JWT (logout/login) роли появляются в realm_access.roles

Без этого все decide/publish операции вернут 403 forbidden_role.
2026-05-13 12:36:53 +03:00
Zimin A.N. 1281654d32 chore(auth): rename schema workflow roles к colon-convention
Project uses Altum-style colon-separated realm roles:
- ordinis:client:public / internal / restricted (scope ACL, уже active)

Schema workflow roles (Phase 1d TODO) приводим к той же convention:
- ordinis:schema:reviewer (был ordinis-schema-reviewer)
- ordinis:schema:publisher
- ordinis:schema:maker

Изменения в usePermissions.ts:
- JSDoc обновлён с новой convention
- Constants ROLE_SCHEMA_* для discoverable rename'а в Phase 1d
- TODO comments указывают на constants, не bare strings

Phase 1d (когда подключится backend role check):
1. Uncomment role check в hooks
2. Backend SchemaDraftService.decide() — require role
2026-05-13 12:27:33 +03:00
Zimin A.N. 6ee88a86bd fix(ui): кнопки в schema draft drawer — RU label + flow row
User feedback (screenshot): 'Approve' остался английским в RU локали +
'Отозвать' улетал на отдельную строку справа из-за ml-auto.

Fix:
- i18n.ts: 'workflow.schemaDraft.actions.approve' default 'Approve' →
  'Одобрить'. EN остался 'Approve'.
- SchemaDraftDrawer.tsx: убрал ml-auto на withdraw button. Раньше это
  force pushed его в конец строки → flex-wrap отправлял на новую строку
  правым краем. Сейчас withdraw сидит сразу после reject в той же
  flow-row, с danger color для визуальной distinction. На узком экране
  всё ещё wrap'нется естественно (без 'плавающего' alignment).
2026-05-13 11:45:29 +03:00
Zimin A.N. e6294ce79c fix(ui): shared UserCell для display UUID actor'ов везде
Browser QA на staging показал что UUID actor'а render'ится raw во многих
местах кроме drawer (MR !158 — там был inline fix):
- /reviews Records tab — column AUTHOR
- /reviews Schemas tab — column AUTHOR
- /audit log — column USER

77ec2cc8-98e3-4d70-8037-94038fcbde67 — нечитаемо для оператора. Тот же
паттерн что я делал в MR !158, теперь shared helper:

src/lib/useUserDisplay.tsx:
- useUserDisplay(uuid) hook — returns { display, isMe, fullId }
- UserCell component — readonly cell с title=fullId tooltip
- Match current user (JWT sub claim) → 'Я • <preferred_username>'
- Other user → short UUID (8 chars) + full в title
- null/undefined → 'anonymous'

Применено:
- reviews.tsx:334,625 — records + schemas table AUTHOR cells
- audit.tsx:476 — USER cell
- SchemaDraftDrawer.tsx — refactor (был inline в MR !158 → теперь shared)

TODO Phase 1d: backend endpoint /admin/users/{sub}/display чтобы и для
не-current-user отображать username.
2026-05-13 11:43:04 +03:00
Zimin A.N. aec2cdd644 fix(sidebar,audit): badges для Outbox + Webhooks, clarify audit scope label
Sidebar:
- Outbox: бэйдж = outbox.pending (события queued, не отосланы). DLQ
  отдельно — alarming но не actionable из бэйджа, видно на /outbox.
- Webhooks: бэйдж = count активных subscriptions (w.active === true).
  User feedback: 'вебхуки активные есть а цифры с бэйджем нет'.

Audit log column header:
- 'Scope' → 'Доступ актора' (ru) / 'Actor scope' (en)
- Backend audit_log хранит userScope (claim из JWT актора), НЕ scope
  изменённого ресурса. User confusion: 'правки в public были а тут
  RESTRICTED' — потому что у пользователя scope-привилегия RESTRICTED.
  Уточнённая label убирает путаницу.
- TODO: backend feature — добавить dictionaryScope в AuditEntry чтобы
  показать обе ('Актор: RESTRICTED → Ресурс: PUBLIC' — двусторонний
  audit trail для compliance).
2026-05-13 11:03:47 +03:00
Zimin A.N. aecd65e7d5 fix(types): SchemaReviewQueuePage.total → totalElements (match backend)
User reported бэйдж 'Согласований' в sidebar показывал 0 несмотря на 1
pending schema draft. Root cause:

Backend сериализует Spring Page<SchemaDraft> как:
{items, page, size, totalElements, totalPages}

Но TS type SchemaReviewQueuePage объявлял поле как 'total' (mismatch
именования). Consumers читали data?.total → undefined → fallback 0:

- useReviewsBadgeCount (queries.ts:650) — sidebar badge падал на 0
- reviews.tsx:61 — header count в Schemas tab (но fallback на
  items.length скрывал bug в UI)
- reviews.tsx:593-594 — schema queue subtitle 'N drafts ждут ревью'

Fix:
- client.ts SchemaReviewQueuePage: total → totalElements + totalPages
- 3 consumer'а обновлены на .totalElements
- Backend контракт не меняется — это чисто frontend type alignment

После prod deploy v2.16.3 sidebar badge заработает корректно.
2026-05-13 10:58:48 +03:00
Александр Зимин 441bbedadf Merge branch 'fix/schema-draft-author-display-and-withdraw-gate' into 'main'
fix(schema-draft): author display + gate Отозвать только maker'у

See merge request 2-6/2-6-4/terravault/ordinis!158
2026-05-13 07:39:59 +00:00
Zimin A.N. fbf978aa39 fix(schema-draft): автор display + gate Отозвать только maker'у
Два бага в SchemaDraftDrawer (видны на review pending):

1. АВТОР показывал raw UUID (77ec2cc8-98e3-4d70-8037-...). Резолвим:
   - Если maker = current user (JWT sub claim) → 'Я • <preferred_username>'
   - Иначе → short UUID (первые 8 символов) с full в title attribute
   - Стилизация: 'Я' — text-ink (читаемая), чужой — font-mono text-mute
   - TODO Phase 1d: backend endpoint для resolve чужих sub → display name

2. Кнопка 'Отозвать' показывалась всем юзерам, не только автору.
   - Backend всё равно 403'нет non-maker withdraw — но UI confusion:
     reviewer видел кнопку которая всегда fail'ит.
   - Gate: показываем только когда auth.user.profile.sub === draft.makerId.
   - Reviewer теперь видит чистый action row (Approve / Запросить правки /
     Отклонить), без danger-button мимо контекста.

Реальный случай: см. user screenshot на v2.16.x — Reviewer modal с UUID
автора и кнопкой 'Отозвать' справа внизу.
2026-05-13 10:33:02 +03:00
Zimin A.N. 252939f63a fix(sidebar): подключить reviews badge к real count
Бэйдж 'На ревью' в sidebar (desktop + mobile) был захардкожен в 0 —
TODO comment ждал cheap /drafts/pending/count endpoint. На самом деле
existing /admin/reviews/pending и /admin/schema-reviews/pending уже
возвращают paged response с totalElements/total в metadata — request
size=1 даёт нужный counter с минимальным payload (~1 строка + 4
metadata field'а).

Новый hook useReviewsBadgeCount():
- Параллельно дёргает обе queue (record + schema)
- Суммирует totalElements (record) + total (schema)
- Полл каждые 60s (lighter чем 30s queue refresh — badge не critical)
- Gated на auth.isAuthenticated (anonymous не должен дёргать admin endpoint'ы)

Sidebar.tsx (оба варианта — desktop + mobile MobileSidebar):
- reviewsCount: 0 → reviews.total
2026-05-13 10:08:57 +03:00
Zimin A.N. e46598c47d feat(updateBanner): «Что нового» button открывает modal с changelog
Per user feedback: «по кнопке что нового должно открываться модальное
окно с изменениями».

UpdateBanner кнопка «Что нового» (т9n key `updateBanner.docs`) ранее
открывала `/docs/` в новой вкладке — generic documentation page, не
contextual для «что именно в этом обновлении».

Теперь — открывает `WhatsNewModal` (тот же modal что показывает 🎁
button в TopBar) — public changelog с release notes сразу.

## Refactor

- **`WhatsNewModal.tsx` (NEW)** — extracted controlled-mode modal с
  release notes. Props: `open`, `onClose`. Internal logic читает
  changelog, tracks lastSeen в localStorage, на open mark'ает latest
  как seen.
- **`WhatsNewButton.tsx`** — refactored: trigger button + use
  `WhatsNewModal`. Поведение unchanged (🎁 icon с unread badge → click → modal).
- **`UpdateBanner.tsx`** — replaced `window.open(DOCS_URL)` на
  `setWhatsNewOpen(true)` + render `<WhatsNewModal>`. DOCS_URL constant
  removed (больше не используется).

## UX intent

Когда update detected:
1. User видит orange banner «Доступна новая версия v2.16.0»
2. Hover «Что нового» button → opens modal
3. User читает release notes (что добавлено, исправлено) ДО reload
4. Click «Обновить» → reload

Раньше «Что нового» вёл к generic docs page — user не знал ЧТО
изменилось, только что update есть. Modal даёт structured answer.

## Test plan

- [x] `pnpm tsc --noEmit` clean
- [x] `pnpm build` clean
- [x] `pnpm vitest run useChangelog.test` — 11/11 pass (parser logic
      unchanged)
- [ ] Manual: симулировать update banner (mock useAppVersion
      `updateAvailable=true`), click «Что нового» → modal opens
- [ ] Manual: 🎁 button в TopBar также opens same modal
- [ ] Manual: close modal → red dot disappears на 🎁 button
      (localStorage updated)
2026-05-12 23:28:12 +03:00
Александр Зимин 5103ed6d9c fix(admin-ui): changelog build context — copy script + CI before_script 2026-05-12 18:48:04 +00:00
Александр Зимин a34c6d4c82 feat(topbar): «Что нового» button + 2026-05-12 state.md update 2026-05-12 17:05:24 +00:00
Александр Зимин 5479142093 feat(webhook): time-series histogram stats endpoint + frontend chart 2026-05-12 14:30:22 +00:00
Александр Зимин 21d95613fc feat(ui): searchable MultiSelect (cmdk) + DateRangePicker (react-day-picker) 2026-05-12 13:55:35 +00:00
Александр Зимин 775b8345e3 feat(timetravel): Records + Schema tabs wired with real data 2026-05-12 13:00:03 +00:00
Александр Зимин 3dbad1e16c feat(timetravel): «Что изменилось» tab — закрывает «backend pending» placeholder 2026-05-12 12:45:55 +00:00
Александр Зимин 3984d99004 fix(ux): friendly error UI вместо raw AxiosError 2026-05-12 12:41:48 +00:00
Александр Зимин d864a8561a feat(webhooks): stats cards dashboard на subscription detail 2026-05-12 12:25:15 +00:00
Александр Зимин 8360c39a45 chore(theme): font audit — arbitrary px → semantic tokens (1/6 Stage 3.x) 2026-05-12 12:09:52 +00:00
Александр Зимин 27ffd36dca feat(webhooks): event-type multi-select + schema draft events (E) 2026-05-12 11:48:34 +00:00
Александр Зимин a49e01b6c9 feat(reviews): tabs UX — Записи / Схемы (D) 2026-05-12 11:44:46 +00:00
Александр Зимин ef5bd69c40 feat(workflow): Monaco editor для proposed schema (C) 2026-05-12 11:41:12 +00:00
Александр Зимин 131442cae2 chore(theme): bg-white → bg-surface (B — dark theme sweep) 2026-05-12 11:38:14 +00:00
Александр Зимин b3a9104d54 feat(schema-workflow): PATCH /drafts/{id} — edit без recreate (A) 2026-05-12 11:33:01 +00:00
Александр Зимин ac3395f4de feat(schema-workflow): Phase 1c — CreateModal + /reviews extension + RBAC stub 2026-05-12 11:20:29 +00:00
Александр Зимин dd3a2d97e5 feat(schema-workflow): Phase 1b — SchemaDraftDrawer with status-aware actions 2026-05-12 11:08:31 +00:00
Александр Зимин 57be0d88d3 feat(changelog): wire up History tab to /changelog endpoint + diff modal 2026-05-12 10:49:53 +00:00
Александр Зимин 7192aed71c fix(history): подсказка и Button-стайл когда у записи 1 версия 2026-05-12 08:57:44 +00:00
Александр Зимин c76e7a4029 fix(timetravel): скрывать boundary-метки при коллизии с курсором 2026-05-12 08:55:13 +00:00
Александр Зимин 93c2708fb0 fix(ui): internal scope filter + dark-theme cards в Связи 2026-05-12 00:31:51 +00:00
Александр Зимин 2a76b9382e fix(ui): visible History button + remove left rail + dark JSON viewer 2026-05-12 00:18:03 +00:00
Александр Зимин 9fd63ab267 feat(graph): space-theme polish — planets, sprite labels, thematic edges, sizes, loader 2026-05-11 22:50:33 +00:00
Александр Зимин fe18befb2a feat(graph): 3D mode toggle — three.js + WebGL fallback 2026-05-11 22:11:41 +00:00
Александр Зимин 1a72418cb8 feat(form): x-section schema metadata + dynamic section bucketing 2026-05-11 21:52:24 +00:00
Александр Зимин cc131e52f2 fix(a11y): Drawer always renders SheetDescription — silence Radix warning 2026-05-11 21:42:41 +00:00
Zimin A.N. b312ba37ad feat(ui): 3-dot row menu + history compare/revert + DateTime fitting + tablet touch
Per user feedback screenshots:

SchemaDrivenForm DateTimeField:
- grid → flex flex-wrap: на узких cells (drawer 520px ÷ 2 cols = ~210px)
  time wraps под date вертикально. На wide — inline.
- Date min-w 8rem (128px), time w-24 (96px) — tablet touch-safe.

TableHeaderCell без visible label (sr-only для screen readers).
TableCell содержит DropdownMenu trigger 36×36px (touch target):
  - History (always)
  - Edit, Separator, Close (только canMutate, Close в danger color)
Заменяет 3 inline IconButton'a — экономит ширину + tablet friendly.

RecordHistoryDrawer accepts onCompare / onRevert callbacks. Каждая
non-current version row показывает:
  - Сравнить с текущим — opens ConflictDiffModal (reused diff UI)
  - Откатить к v{N} — submit updateMut с этой version данными как
    payload (+ confirm dialog). Backend создаёт новую active version,
    bitemporal history preserves старые.
JSON viewer переключился с <details> на state-driven (hide/show
без CSS rabbit hole).

history.hideData/compare/revert/revertConfirm (RU + EN).

- 3-dot trigger 36px = comfortable touch target (Apple HIG 44, ours
  acceptable для density)
- Drawer на <880px viewport = fullscreen sheet (already)
- DateTime wraps на cell <8rem+9rem+gap = 290px
- Sidebar collapse на <1024 = hamburger overlay
2026-05-12 00:03:44 +03:00
Александр Зимин 11797f88ae Merge branch 'feat/row-click-and-wide-rail' into 'main'
feat(ui): row click → edit drawer + wide-mode left section rail

See merge request 2-6/2-6-4/terravault/ordinis!105
2026-05-11 20:51:42 +00:00
Zimin A.N. 54cf563162 feat(ui): row click opens edit + wide-mode left section rail in drawer
Two improvements per user feedback ('по клику открывается редактирование и
в развернутом виде слева навигация'):

## 1. Row click → edit drawer

TableRow в /dictionaries/$name теперь имеет onClick={setEdit({kind:'edit',
record:r})} когда canMutate. cursor-pointer + bubbling guards:
- Checkbox cell и Actions cell имеют onClick stopPropagation чтобы
  внутренние controls не открывали drawer случайно.
- Anonymous users (no canMutate) — row не clickable (read-only, no drawer).

## 2. Wide-mode left rail в drawer

SchemaDrivenForm в sections layout теперь умеет ResizeObserver-based detection
своей ширины. Когда контейнер ≥720px (drawer wide mode = 880px) → render'ит
180px sticky left rail с section list + per-section field counts. Click rail
item → smooth scroll к section anchor. Иначе chip strip сверху, как раньше.

Container-based (не viewport-based) чтобы narrow drawer на wide viewport не
рендерил rail (520px не fits 180+1fr grid).
2026-05-11 23:51:03 +03:00
Zimin A.N. b2231a5a47 fix(form): FK select label overflow — truncate + ellipsis (наезжают on chevron)
Native <select> для FK references показывал label '<code> — <localized name>'
(например 'OPT_MIDRES_MS — Оптический мульти-резолюционный мульти-спектральный')
— текст бьётся на chevron в trigger area из-за тонкого contract'а browser-
specific overflow handling (наезжают per user screenshot 2026-05-11).

Fix:
- buildOption() обрезает name до MAX_FK_LABEL_CHARS=28 (code intact) +
  '…' suffix когда combined превышает limit
- select сам получает 'truncate overflow-hidden whitespace-nowrap text-ellipsis'
  как защита если browser native truncate не сработал
- pr-10 → pr-9 (38px) — точно совпадает с right-3 chevron position (12px)
  + ~16px icon + 8px gap, не overlap'aет.
2026-05-11 23:43:38 +03:00
Zimin A.N. fcb0ad0eb5 fix(ui): logo orbits stroke 5→12 — actually visible at 28px
stroke=5 в viewBox 256 при 28px render = 5*28/256 = 0.55px (subpixel,
почти невидимо). stroke=12 даёт ~1.31px real — чёткая линия. Заодно
переключил с --color-line-2 на --color-line чтобы немного контрастнее
на bg-surface.
2026-05-11 23:38:00 +03:00
Zimin A.N. 0c978bec0c fix(ui): bigger logo + shorter 'Создать' label
Logo: при 26px+тонких stroke (1.5) почти невидим. Bumped:
- width/height 26 → 28
- strokes 1.5 → 5 (визуально ~1.5px при 28px render)
- outer dots r=8 → 16/14 (main + secondaries)
- inner dots r=6.4 → 13/11
- core r=15 → 24
- outer orbit stroke использует --color-line-2 (мягче чем --color-line)

Favicon тоже укрупнён (stroke 2.2, dot r=3, core r=4) — читается в 16px tab.

Label: 'Создать запись' → 'Создать' для dict.action.create (RU + EN).
User feedback: 'Создать запись надо сократь на Создать'.
2026-05-11 23:36:00 +03:00
Zimin A.N. 7684e0c9bd fix(brand): recolor logo — terracotta accent, no blue (theme-aligned)
Brand assets из !100 пришли с DeepBlue/SkyBlue colors (design system
prototype). Ordinis app theme uses warm terracotta accent (#d97757 light /
#b05a2e dark) на cream/ink palette. Never blue. Recolored все 5 SVG +
inline Sidebar mark:

- favicon.svg: stroke #d97757, dots/core #b05a2e
- ordinis-mark.svg: orbits #e6e1d4 (line), dots accent gradient terracotta
- ordinis-appicon.svg: dark warm-ink tile (#2c2b27 → #1a1714), accent dots,
  white outer dots for contrast on dark
- ordinis-lockup-light.svg: terracotta orbits + warm-ink wordmark (Tektur)
- ordinis-lockup-dark.svg: warm-dark bg + terracotta accents + cream wordmark
- Sidebar inline svg: currentColor + text-accent, var(--color-line) для
  orbits — автоматически работает в both light + dark themes
- index.html: theme-color/mask-icon → #b05a2e (terracotta dark)

User feedback: 'DeepBlue никакого синего адаптируй под наши темы'.
2026-05-11 23:28:06 +03:00
Zimin A.N. e6fe9c3da8 feat(brand): adopt official Ordinis logo assets
Делёные design team 2026-05-11 в Downloads/files:
  - ordinis-mark.svg — двойные орбиты + 6 dots + gradient core
    (DeepBlue #185FA5 → SkyBlue #378ADD), brand spec
  - ordinis-favicon.svg — minimal 32px variant
  - ordinis-appicon.svg — 1024px PWA icon с dark rounded tile
  - ordinis-lockup-light/dark.svg — horizontal lockup с wordmark
    (для landing/splash/marketing — пока не используется но
    хранится в public для future use)

Changes:
- public/favicon.svg → minimal 32px variant
- Sidebar logo block: inline ordinis-mark с двойными орбитами +
  gradient core. Wordmark 'ORDINIS MDM' остаётся в Tektur.
- index.html: theme-color #185FA5 (был #120a8f), apple-touch-icon
  link на appicon, mask-icon color match.

Note: SVG colors hardcoded в Sidebar inline для full design fidelity
(brand assets specifically defined gradient). currentColor не подходит
т.к. brand требует точные DeepBlue/SkyBlue/LightBlue tokens.
2026-05-11 23:19:33 +03:00
Zimin A.N. 82b754b3ab fix(loader): remove transform-box fill-box — orbits rotated around offset bbox
CSS transform-box default для SVG = view-box (transform-origin в viewBox
user coords). С transform-box: fill-box origin становится относительным
bounding-box каждой группы (whirl + whirl-rev — arc bbox смещён). Арки
летали по неправильной траектории. Прототип HTML rely on default — copy.
2026-05-11 22:43:32 +03:00
Zimin A.N. acde4e134d feat(loader): real Natural Earth globe via d3-geo + topojson + world-atlas
Per user — GlobeLoader должен показывать реальные континенты (Asia/Africa/
Australia outline) с rotating orthographic projection, как в прототипе
Downloads/Globe loader/Globe Loader.html. Прошлая версия использовала
faked ellipses graticule — выглядело как абстрактные кольца, не как глобус.

Implementation:
- d3-geo.geoOrthographic + d3.geoPath для projection math
- topojson-client.feature для распаковки world-atlas land-110m
- dynamic import обоих deps + JSON — не блокирует main bundle, lazy chunk
- requestAnimationFrame loop пересчитывает d-attr на каждом кадре
  (8s per revolution per прототип, tilt -15° lean)
- LAND_CACHE module-level — повторные mount'ы не re-fetch
- CSS .globe-sphere rotation удалена — d3 projection делает работу,
  CSS transform distort'ила бы land mass (нет projection math)

Bundle hit: ~55kb (land-110m.json) + ~15kb gzipped (d3-geo + topojson) —
lazy chunk, fetched один раз и cached.
2026-05-11 22:39:58 +03:00