# HDESIGN CODE

Документ фиксирует канон интерфейса NODE.DC, чтобы не обсуждать одни и те же правила повторно.

## Источник цветов
- Основной runtime-конфиг цветов: [design.config.json](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/design.config.json)
- Рабочая web-копия: [plane-src/apps/web/design.config.json](/Users/dcconstructions/Downloads/mnt/data/dc_taskmanager/NODEDC_TASKMANAGER/plane-src/apps/web/design.config.json)
- В рантайме используются CSS variables:
  - `--nodedc-accent-rgb`
  - `--nodedc-card-passive-rgb`
  - `--nodedc-card-active-rgb`

## Цветовые правила
- `accent_rgb`: акцентный цвет интерфейса.
- `passive_card_rgb`: пассивные карточки.
- `active_card_rgb`: активные карточки и primary CTA в зелёной теме.
- Primary/active элементы используют акцентный или `active_card_rgb`.
- Secondary элементы не должны иметь ярких outline и цветных рамок без явной причины.

## Радиусы
- Главные модалки и большие surface-контейнеры: `1.75rem`
- Стандартные glass-карточки и settings-карточки: `1.35rem`
- Поля ввода, селекты, secondary/primary кнопки, chip-кнопки: `1.25rem`
- Малые круглые action-кнопки: `999px` или полный круг при квадратной коробке

## Outline и рамки
- Внешние outline у контролов запрещены.
- Синий browser outline должен быть снят и заменён на нормальный hover/focus surface.
- Если нужен контур, он должен быть частью дизайна:
  - мягкий glass border
  - акцентный border для drag/drop или active-state
- Красные технические outline, debug-рамки и случайные browser-box shadow запрещены.

## Glass и фон
- Popup, dropdown, modal, sidebar overlays и settings-карточки используют matte black glass.
- База:
  - тёмный полупрозрачный фон
  - `backdrop-filter: blur(...)`
  - мягкая стеклянная граница
- Popup не должны выглядеть просто прозрачными. Если blur не читается, проблема в слое рендера, а не в одном `rgba`.

## Кнопки
- Все кнопки без жёсткого outline.
- Primary button:
  - фон: акцентный или `active_card_rgb`
  - текст: всегда чёрный или очень тёмный, если фон светлый
  - hover: более светлая версия того же цвета
  - правило распространяется на все filled CTA:
    - `Добавить`
    - `Сохранить`
    - `Обновить`
    - `Принять`
    - `Добавить запрос`
    - любые акцентные toolbar-кнопки
  - это правило обязательно и для `Внешних контуров`: `Добавить запрос` не может иметь светлый текст на светлом фоне
- Save/update button:
  - если это зафиксированный green CTA, текст должен быть контрастным и читаемым
  - hover осветляет текущий тон, а не уходит в синий
- Secondary button:
  - тёмный glass фон
  - без border-outline
  - hover немного светлее базового surface
- Danger button:
  - без кислотно-красных рамок
  - мягкий danger surface

## Поля и селекты
- Все поля ввода, textarea, select, chip-select:
  - скруглённые
  - без внешних outline
  - glass background
  - единая вертикальная высота для одного класса контролов
- Placeholder и label должны быть читаемы и не прилипать к краям.

## Toolbar и верхние панели
- Элементы верхней панели центрируются по одной горизонтальной оси.
- Активный layout/tool mode выделяется кругом акцентного цвета, не квадратной плашкой.
- Кнопки `Отображение`, `Аналитика`, `Добавить рабочий элемент`:
  - одинаковая высота
  - каноничные радиусы
  - нормальные горизонтальные paddings, чтобы текст не лип к краям

## Карточки
- Внутренние карточки строятся по симметричным верхним и нижним padding.
- Верхняя ось:
  - аватар
  - имя
  - вторичная строка
  - action-circle справа
  должны сидеть на согласованной геометрии
- Нижняя ось:
  - assignee bubbles
  - дата
  должны быть симметричны верхней
- Для списков карточек `Внешних контуров` используется тот же вертикальный ритм, что и у `Внутреннего контура`:
  - контейнер списка не плотнее `space-y-3`
  - нельзя лепить карточки вплотную друг к другу

## Dropdown и popup
- Все dropdown/popup приводятся к единому matte glass канону.
- Запрещены:
  - квадратные active-box вокруг круглых кнопок
  - жёсткие border-outline
  - светлый фон, если основной экран тёмный
- Search shell внутри popup должен использовать тот же стиль, что и сам popup.
- Filled CTA внутри popup и модалок подчиняются тому же правилу:
  - светлый акцентный фон
  - тёмный/чёрный текст
  - hover только в более светлый тон того же цвета

### Portal правило
- Если selector/dropdown открывается внутри:
  - scroll-контейнера
  - detail-pane
  - карточки
  - properties section
  - sidebar
  - sticky header
  он не должен рендериться inline.
- Такой popup обязан рендериться на верхнем слое через `portal` (`document.body` или эквивалент).
- Inline popup в ограниченном контейнере считается дефектом, потому что даёт:
  - клиппинг
  - налезание на соседние блоки
  - старую “врезанную” верстку
- Для `Свойств` в `Внешних контурах` dropdown по умолчанию открывается вверх:
  - `placement="top-start"`
  - причина: блок находится близко к `Активности`, popup не должен падать вниз в соседнюю секцию

### Portal anchor snippet
```tsx
{isOpen &&
  typeof document !== "undefined" &&
  createPortal(
    <Combobox.Options className="fixed z-[420]" static>
      <div
        data-prevent-outside-click
        className="nodedc-dropdown-surface nodedc-external-popup-anchor"
      >
        ...
      </div>
    </Combobox.Options>,
    document.body
  )}
```

### Reusable классы
- Accent CTA:
  - `.nodedc-external-primary-button`
  - текст внутри всегда `#0b1117`
- Secondary action:
  - `.nodedc-external-action-button`
- Secondary icon action:
  - `.nodedc-external-icon-button`
- Readonly property/control surface:
  - `.nodedc-external-readonly-value`
  - `.nodedc-modal-field`
- External property rows:
  - `.nodedc-external-property-row`
  - `.nodedc-external-property-label`
  - `.nodedc-external-property-value`
  - `.nodedc-external-property-control`
- Dropdown shell:
  - `.nodedc-dropdown-surface`
  - `.nodedc-dropdown-search`
  - `.nodedc-dropdown-option`
- External contour card/shell:
  - `.nodedc-external-card`
  - `.nodedc-external-section`
  - `.nodedc-external-content-shell`

### Anchor snippets
```tsx
<Button className="nodedc-external-primary-button">...</Button>
```

```tsx
<IconButton className="nodedc-external-icon-button" ... />
```

```tsx
<div className="nodedc-external-readonly-value">
  <SomeIcon className="h-3.5 w-3.5 text-tertiary" />
  <span className="text-12 font-medium text-primary">...</span>
</div>
```

```tsx
<div className="nodedc-external-property-row">
  <div className="nodedc-external-property-label">
    <SomeIcon className="h-4 w-4 flex-shrink-0" />
    <span>Label</span>
  </div>
  <div className="nodedc-external-property-value">
    <SomeValue />
  </div>
</div>
```

```tsx
<Dropdown
  button={
    <div className="nodedc-external-property-control text-[13px] font-medium">
      <SomeIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
      <span className="text-primary">Value</span>
    </div>
  }
/>
```

## Drag and drop
- Drag overlay использует акцентный контур.
- Delete dropzone:
  - без красного технического свечения и без red-tinted text/fill
  - текст локализован
  - акцентный outline обязателен

## Тексты
- Пользовательский UI на русском, если экран русифицирован.
- Не оставлять смешанные подписи вида `Created at / Updated at / Label / State group`, если экран уже на русском.

## Правило внедрения
- Новый экран или popup не стилизуется локально “на глаз”.
- Сначала используется существующий shared-класс или shared-component.
- Если shared-слоя нет, создаётся reusable-класс/компонент и уже через него приводятся все похожие места.
- Цель: не точечная покраска одного окна, а единый системный канон.
- Если блок визуально расходится со стилем системы, не добавлять поверх временную wrapper-заплатку. Нужно либо перевести блок на shared-компонент, либо переверстать локальную структуру под shared-классы.
- Для экранов со вкладками/переключателями нельзя оставлять flash старой верстки. Перед refetch нужно очищать stale store-data и показывать loading shell.
- Если карточки или списки разных модулей должны быть одинаковыми по канону, нельзя лечить это внешней обёрткой. Нужно менять сам внутренний layout item-компонента.
- Для `Внешних контуров` это значит:
  - список карточек правится на уровне `list-item.tsx`, а не через внешний wrapper
  - gap между карточками должен совпадать с каноном `Внутреннего контура`
  - актуальный gap списка на текущем каноне: `space-y-3`
  - при tab switch между `Открытые / Закрытые` нельзя полагаться только на route param; нужен локальный `pendingTab`, чтобы stale layout не мелькал до завершения refetch
  - toolbar-навигация и inline actions не должны использовать старые квадратные `IconButton` остатки
  - свойства `Приоритет / Метки / Статус` не должны рисовать внутренние boxed-chip артефакты
  - popup `Приоритет / Метки` не может визуально жить внутри property-row; если он открывается из blur-shell, он обязан уходить в portal и рендериться над секцией
  - filled CTA вроде `Добавить запрос` используют `nodedc-external-primary-button` и всегда имеют тёмный текст
  - filled CTA используют чёрный/почти-чёрный текст всегда; белый текст на светлом акценте запрещён
  - secondary meta-иконки в карточке списка не должны иметь отдельную серую подложку, если по канону это простой inline icon
  - empty-state не должен использовать декоративную серую подложку под SVG; media-box прозрачный, SVG выравнивается через `display:flex` и центрирование
  - detail-toolbar в карточке запроса использует общий glass-cluster для листания `prev/next`, а сами кнопки внутри кластера — круглые, без квадратной подложки
  - `Добавить запрос` в header `Внешних контуров` — это filled accent CTA с тёмным текстом, каноничным радиусом и hover в более светлый тон того же акцента
- popup выбора `Приоритет / Метки` внутри detail view не рендерится inline в property-row; он обязан уходить в `portal`
- секции с dropdown-trigger внутри blur/glass shell обязаны иметь `overflow: visible` и `isolation: isolate`, иначе popup визуально “тонет” внутри блока
- при переключении `Открытые / Закрытые` store обязан очистить stale request list до нового fetch, чтобы пользователь не видел flash старой верстки
- карточка списка `Внешних контуров` правится на уровне `list-item.tsx`, а не внешней обёрткой:
  - верхняя и нижняя оси собираются как у карточки `Внутреннего контура`
  - gap между карточками совпадает с каноном `Внутреннего контура`
- empty-state иконки без декоративной подложки; если иконка визуально “плывёт”, корректируется сам SVG/media-box

### Внешние контуры: code anchors
- Header CTA:
```tsx
<Button className="nodedc-external-primary-button">...</Button>
```

- List spacing:
```tsx
<div key={resolvedTab} className="space-y-3">
  {filteredRequestIds.map((requestId) => (
    <ExternalContoursListItem key={requestId} ... />
  ))}
</div>
```

- Pending tab anti-flash:
```tsx
const [pendingTab, setPendingTab] = useState<TInboxIssueCurrentTab | null>(null);
const routeTab = (searchParams.get("currentTab") as TInboxIssueCurrentTab | null) ?? currentTab;
const resolvedTab = pendingTab ?? routeTab;
const isTabTransitioning = loader === "init-loading" || pendingTab !== null || routeTab !== currentTab;
```

- Property popup anchor:
```tsx
<PriorityDropdown
  placement="top-start"
  buttonContainerClassName="nodedc-external-property-control-shell ..."
  button={
    <div className="nodedc-external-property-control text-[13px] font-medium">
      ...
    </div>
  }
/>
```

- Detail toolbar cluster:
```tsx
<div className="nodedc-external-toolbar-cluster">
  <button type="button" className="nodedc-external-icon-button">...</button>
  <button type="button" className="nodedc-external-icon-button">...</button>
</div>
```

- Property control:
```tsx
<div className="nodedc-external-property-control text-[13px] font-medium">
  <SomeIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
  <span className="text-primary">...</span>
</div>
```

- Root tab switch without stale flash:
```tsx
const [pendingTab, setPendingTab] = useState<TInboxIssueCurrentTab | null>(null);
const resolvedTab = pendingTab ?? routeTab;
const isTabTransitioning = loader === "init-loading" || pendingTab !== null || routeTab !== currentTab;

if (resolvedTab !== nextTab) {
  setPendingTab(nextTab);
  void handleCurrentTab(workspaceSlug, projectId, nextTab);
  router.push(`...currentTab=${nextTab}`);
}
```

- Store-side tab reset:
```ts
this.requestIds = [];
this.requests = {};
this.loader = "init-loading";
this.currentTab = tab;
```

- Portal popup с фиксированной стратегией:
```tsx
const { styles, attributes } = usePopper(referenceElement, popperElement, {
  strategy: "fixed",
  placement: placement ?? "bottom-start",
});
```

- Property popup without boxed artifact:
```tsx
<IssueLabelSelect
  rootClassName="w-full overflow-visible"
  buttonContainerClassName="nodedc-external-property-control-shell h-full w-full overflow-visible"
  label={
    <div className="nodedc-external-property-control text-[13px] font-medium">
      <LabelPropertyIcon className="h-3.5 w-3.5 flex-shrink-0 text-tertiary" />
      <span className="truncate text-primary">...</span>
    </div>
  }
/>
```

- Контейнер секции с trigger:
```tsx
<div className="nodedc-external-section overflow-visible px-4 py-4">
  ...
</div>
```
