Files

26 KiB
Raw Permalink Blame History

NON-NEGOTIABLE ARCHITECTURE RULES

  • Read this file before editing.
  • App/Router is the only page mount/unmount owner.
  • Never restore self render + replaceWith.
  • Never restore components.css.
  • Never restore global button:not(...) visual reset.
  • Do not add visual !important to solve cascade problems.
  • Do not create fixes.css / overrides.css / compatibility.css.
  • Reuse TopBar, Dropdown, Avatar and other shared components.
  • Preserve accepted visual behavior unless the task explicitly requests redesign.

AGENTS.md

Назначение

Этот файл — обязательный архитектурный контракт проекта shine-UI.

Любой агент, разработчик или автоматический инструмент перед изменением UI обязан сначала прочитать этот файл.

Главная задача правил — не только сделать новую функцию рабочей, но и не вернуть архитектурные проблемы, которые уже были устранены предыдущими refactor stages.

Если быстрое решение конфликтует с этими правилами — быстрое решение запрещено.


1. Общая архитектура

Проект — vanilla JavaScript frontend.

Не использовать и не добавлять без отдельного архитектурного решения:

React
Vue
Svelte
Solid
Preact
Redux
глобальный state framework
virtual DOM framework
новый router

Текущая архитектурная цепочка:

Foundation
    ↓
App Shell
    ↓
Shared Components
    ↓
Semantic UI roles
    ↓
Features / Pages

Направление зависимостей должно сохраняться.

Feature может использовать shared component.

Shared component не должен знать детали конкретного feature.


2. Зафиксированный baseline

Следующие этапы считаются завершёнными и не должны откатываться.

Stage 1 — App Shell

Shell централизован:

AppShell
├── TopbarSlot
├── ScreenContent
├── ComposerSlot
├── BottomToolbarSlot
├── Global Top Fade
└── Global Bottom Fade

Shell управляется через chrome.


Stage 2 — Shared TopBar / Dropdown

Используются единые:

js/components/topbar.js
styles/components/topbar.css

js/components/dropdown-menu.js
styles/components/dropdown-menu.css

Не создавать альтернативные Header/Menu реализации внутри страниц.


Stage 3 — CSS ownership

Старый:

styles/components.css

удалён.

Его нельзя создавать снова.

CSS разделён по владельцам:

styles/components/*
styles/features/*

Stage 4 — CSS / Button architecture

Старый глобальный button reset удалён.

Запрещено возвращать архитектуру:

button:not(...):not(...) {
    ...
}

Количество !important было уменьшено примерно:

356 → 8

Не возвращать specificity wars и late global override layer.

Button styling является opt-in / semantic.


Stage 5 — Page lifecycle

Channel и Thread больше не создают новый instance сами через:

const next = render(...);
current.cleanup?.();
current.replaceWith(next);

Текущая модель:

App / Router
    ↓
mount page instance
    ↓
stable page root
    ↓
page.refresh()
    ↓
local content update

App / Router
    ↓
page.cleanup()
    ↓
unmount

Эту ownership-модель нельзя откатывать.


3. Главное правило ownership

Для любого кода сначала определить:

КТО является владельцем?

У каждого UI-аспекта должен быть один основной owner.

Примеры:

Page lifetime        → App / Router
TopBar               → TopBar component + chrome
Dropdown lifecycle   → Dropdown component
Generic Avatar       → Avatar component
Toolbar              → Toolbar component
Tabs                 → Tabs component
Button generic role  → semantic button layer
Channel layout       → Channel feature
Thread layout        → Thread feature

Запрещено исправлять проблему созданием второго владельца.


4. App Shell

Страницы не должны напрямую создавать альтернативный global shell.

Для TopBar использовать:

chrome.setTopbar(...)

Для Composer использовать существующий chrome API.

Для shell mode/fades использовать существующий centralized shell contract.

Не создавать внутри ScreenContent собственные:

global header
global fixed toolbar
альтернативный composer slot
копию top fade
копию bottom fade

если это уже принадлежит AppShell.


5. Page lifecycle

App / Router владеет

mount page root
unmount page root
route change
заменой текущей страницы
shell slot lifecycle

Page владеет

local state
local content
feature listeners
feature timers
feature observers
local refresh

6. Запрещённый self-rerender

Внутри page нельзя делать:

const next = render(...);
screen.cleanup?.();
screen.replaceWith(next);

или эквиваленты:

self render + replaceWith
self render + outerHTML
self cleanup + self recreation
recursive page render

для обновления текущего route.

Если нужно обновить данные текущей страницы:

screen.refresh()

или внутренний:

refresh()
renderBody()
syncView()

должен обновить содержимое существующего instance.

Page root при local refresh должен оставаться тем же DOM node.


7. Stable root

Нормальное поведение:

const rootBefore = screen;

await screen.refresh();

rootBefore === screen;

должно оставаться истинным.

При переходе на другой route App создаёт другой root.

Нельзя делать так, чтобы App хранил reference на detached старый root.


8. Cleanup

Каждая page, создающая lifetime-resources, должна иметь корректный:

screen.cleanup

Cleanup должен быть безопасным при повторном вызове.

Предпочтительный pattern:

let disposed = false;

screen.cleanup = () => {
    if (disposed) return;
    disposed = true;

    // cleanup
};

9. Refresh НЕ является cleanup

Нельзя при каждом local refresh выполнять full page cleanup.

Refresh может очищать только refresh-owned content/resources.

Например допустимо:

старый content
старый read tracker
старые refresh timers

Но нельзя уничтожать весь page instance и создавать новый.


10. Async safety

Любая async операция, результат которой обновляет UI, должна учитывать lifetime страницы.

Использовать существующий pattern:

if (disposed) return;

и при нескольких последовательных refresh:

const seq = ++refreshSeq;

const result = await load();

if (disposed || seq !== refreshSeq) return;

Это предотвращает:

request A
↓
refresh B
↓
B становится актуальным
↓
A завершается позже
↓
A портит новый UI

11. Не мутировать UI после dispose

После:

screen.cleanup()

старые async callbacks не должны:

showToast
перерисовывать screen
открывать modal
менять TopBar
менять Composer
добавлять DOM

если действие относится к уже закрытой странице.

Перед UI commit проверять active state.


12. Timers

Lifetime-sensitive timers должны:

  1. либо быть безопасными через isConnected / disposed;
  2. либо сохраняться и очищаться.

Например:

const timer = window.setTimeout(...);

если timer принадлежит refresh/page lifecycle, должен быть очищаемым.

Не оставлять timeout, который после navigation мутирует detached DOM.


13. Listeners

Различать:

Root-owned listener

screen.addEventListener(...)

Если root уничтожается вместе со listener, отдельный remove может быть не нужен.

Global listener

window.addEventListener(...)
document.addEventListener(...)

Он обязан иметь понятный cleanup:

window.removeEventListener(...)

Не создавать anonymous global listener, который невозможно снять.


14. Modal ownership

Если page открывает modal, нужно понимать, кому он принадлежит.

При page cleanup нельзя оставлять modal, callback которого замыкает disposed page state.

Для page-owned modal при unmount:

close / destroy / clear

его UI и callbacks.

Не удалять чужой modal другой feature.


15. Dropdown ownership

Не реализовывать заново:

outside click
Escape
portal
backdrop
pressed state
resize/scroll cleanup

Использовать общий Dropdown component.

Feature передаёт содержимое/actions.

Dropdown component управляет своим lifecycle.


16. TopBar ownership

Не создавать новый <header> непосредственно в feature.

Использовать общий TopBar.

Feature может передавать:

title
left action
right action
center content
callbacks

Но generic geometry/surface TopBar принадлежит shared component.


17. CSS hierarchy

CSS должен следовать:

main / foundation
↓
layout
↓
app-shell
↓
semantic/shared components
↓
features

Нельзя решать cascade problem перестановкой «fix file» в самый конец.


18. Запрещённые CSS-файлы

Не создавать:

components.css
legacy.css
compatibility.css
fixes.css
final.css
final-fixes.css
overrides.css
patches.css

как место, куда складываются новые исключения.

Если правило непонятно куда положить — сначала определить owner.


19. Shared CSS ownership

Generic component surface должна жить в:

styles/components/*

Примеры:

avatar.css
topbar.css
dropdown-menu.css
toolbar.css
tabs.css
modal.css
emoji-picker.css
call-ui.css
scroll-to-bottom.css
attachments.css

Feature CSS не должен возвращать альтернативную реализацию shared component.


20. Feature CSS ownership

В:

styles/features/*

допустимы:

feature layout
feature spacing
feature-specific state
feature-specific intentional variant

Не допустимы случайные переопределения generic shared component surface.

Например feature может задавать Avatar:

size
placement
margin

но не должен случайно заново определять:

generic background
generic frame
generic glass overlay
generic fallback

21. Не добавлять исторические override chains

Запрещён pattern:

.component { ... }

/* fix */
.screen .component { ... }

/* final */
.screen.feature .component { ... }

/* really final */
.screen.feature .component.state {
    ... !important;
}

Перед добавлением нового rule проверить существующие definitions этого selector.

Если нужен новый final result — изменить правильный owner/base/state.


22. !important

Новые !important по умолчанию запрещены.

Перед добавлением обязательно доказать, почему проблему нельзя решить:

ownership
specificity
source order
state class
semantic role

Текущий низкий baseline нельзя ухудшать.

Особенно запрещены:

background: ... !important;
border: ... !important;
color: ... !important;
box-shadow: ... !important;

для победы в визуальном cascade.


23. Button architecture

Старый глобальный reset возвращать запрещено.

Нельзя:

button:not(...) {
    background: transparent;
    ...
}

Нельзя:

:root button {
    ...
}

использовать как global visual policy.


24. Перед созданием кнопки определить её роль

Каждый новый <button> должен попасть в одну из категорий:

1. semantic generic button
2. shared component button
3. intentional feature variant

25. Generic semantic button

Если кнопка должна иметь общий neutral Stage-4 surface, использовать существующую semantic role, например:

ui-button
primary-btn
secondary-btn
destructive-btn
ghost-btn
icon-btn
text-btn
shine-btn

Не писать отдельный visual reset для одного feature.


26. Component-owned button

Если button принадлежит:

TopBar
Toolbar
Tabs
Dropdown
EmojiPicker
Call
ScrollToBottom

его surface должен принадлежать CSS этого component.

Не добавлять ui-button автоматически, если component уже имеет собственный intentional contract.


27. Composite buttons

Особенно внимательно с:

<button class="card row ...">

или:

button + card
button + row
button + chip
button + pill

Generic structural class может содержать собственный background/border.

Перед использованием проверить конечный cascade.

Если button должен оставаться semantic neutral — добавить правильную semantic button role.


28. Button states

При изменении button проверять не только normal state.

Минимум:

normal
hover
active
focus-visible
disabled

Где применимо:

is-active
is-selected
is-liked
aria-expanded
data-open

29. Button pseudo-elements

При изменении button также проверить:

::before
::after

Особенно если используются:

glow
shine
press
overlay
decorative line

Не возвращать мёртвые historical pseudo-layers.


30. currentColor

Для icon buttons учитывать:

stroke: currentColor;
fill: currentColor;

Изменение color может визуально изменить SVG даже при неизменном background.


31. Новый shared component

Создавать shared component имеет смысл, если:

  • один и тот же UI pattern реально используется в нескольких features;
  • у него есть собственный lifecycle или generic visual contract;
  • feature-specific код начинает дублироваться.

Не делать shared component из элемента, существующего только на одной странице.


32. Новый feature CSS

При добавлении feature rule сначала ответить:

Это generic component surface?
или
это только feature layout/state?

Если generic — правило, скорее всего, должно находиться в styles/components/*.

Если feature-specific — в соответствующем styles/features/*.


33. Не исправлять один экран ломая owner

Если визуальная проблема возникает только в Channel, нельзя автоматически писать:

.channel-screen .shared-component {
    ...
}

Сначала проверить:

  • shared component сломан глобально?
  • Channel использует неправильный variant?
  • feature содержит historical declaration?
  • неправильный DOM class?

Исправлять первопричину.


34. App/router не переписывать локальной задачей

Feature change не должна создавать:

новый router
parallel navigation
собственный page registry
собственный mount manager

Навигация выполняется через существующий:

navigate(...)

35. Refresh ≠ navigation

Если пользователь остаётся на той же странице:

like
reply
edit
delete
subscribe
metadata update

это обычно local refresh/update.

Не симулировать refresh через:

navigate(currentRoute)

если нет специальной архитектурной причины.


36. Route contract

Не менять без отдельной задачи:

route names
route params
deep links
history semantics

Lifecycle refactor не должен менять URL contract.


37. Backend/API

UI-агент не должен без отдельной задачи менять:

API contract
backend
DAO
blockchain protocol
response format
storage schema

Если для UI не хватает данных — зафиксировать проблему, а не придумывать новый backend contract.


38. Перед редактированием

Перед любой нетривиальной правкой агент обязан:

  1. найти DOM producer;
  2. определить owner;
  3. найти все связанные selectors/functions;
  4. проверить shared component;
  5. проверить существующие states;
  6. проверить cleanup/lifecycle;
  7. определить baseline текущего поведения.

Не начинать с добавления override.


39. После редактирования

Минимальный обязательный validation:

node --check для production JS
relative import audit
CSS parser если CSS менялся
CSS manifest если CSS/imports менялись
!important count если CSS менялся
duplicate-selector audit для затронутого owner

40. Для lifecycle изменений

Дополнительно проверить:

self render + replaceWith отсутствует
root identity сохраняется при refresh
cleanup idempotent
async callback после dispose ничего не мутирует
refresh race защищён
timers/listeners не накапливаются
modal/dropdown не остаются после unmount

41. Для CSS изменений

Дополнительно проверить:

не появился новый global reset
не появился новый late override layer
не вырос !important без причины
shared component не получил feature dependency
feature не стал владельцем generic component surface

42. Для button изменений

Дополнительно проверить:

producer classes
ancestor context
normal
hover
active
focus-visible
disabled
pseudo-elements
currentColor

43. Запрещённые быстрые решения

Если агент собирается сделать что-то из следующего — нужно остановиться и пересмотреть решение:

добавить ещё один !important
создать fixes.css
создать legacy override
поставить CSS-файл последним
добавить button:not(...)
перерисовать page через self replaceWith
создать второй TopBar
создать второй Dropdown
копировать shared component в feature

Обычно это признак неправильного ownership.


44. Search-before-add

Перед созданием:

нового class
нового component
нового helper
нового CSS rule

сначала искать аналог по проекту.

Не создавать:

channel-menu-v2
channel-menu-new
topbar-alt
button-final
avatar-new

если shared abstraction уже существует.


45. Не сохранять мёртвый styling

Если historical declaration не была observable в текущем accepted baseline и новый architecture делает её visible — не защищать её «на всякий случай».

Удалить dead styling.

Accepted visual behavior важнее исторического текста CSS.


46. Не менять accepted baseline незаметно

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

Архитектурный refactor по умолчанию означает:

behavior before
=
behavior after

Если parity нельзя сохранить — сообщить об этом отдельно.


47. Комментарии и документация

Комментарии должны объяснять:

WHY

а не очевидное:

WHAT

Хорошо:

// Ignore stale refresh completion after a newer refresh or page disposal.

Плохо:

// Increment variable.
refreshSeq += 1;

48. Не использовать архитектурные документы как мусорный лог

AGENTS.md содержит постоянные правила.

Stage-specific audit/report должен быть отдельным документом.

Не добавлять сюда:

временные баги
результаты одного запуска
конкретные debug logs
временные TODO

если это не постоянное правило.


49. Definition of Done для UI-изменения

Изменение считается готовым только если:

1. Функция работает.
2. Ownership понятен.
3. Старый architecture не возвращён.
4. Lifecycle не имеет второго owner.
5. CSS не получил новую specificity war.
6. Shared components не дублированы.
7. Async callbacks безопасны после unmount.
8. Проверки проходят.
9. Нет временных fixtures/debug code.
10. Diff не содержит unrelated refactor.

50. Если не уверен

Не компенсировать непонимание дополнительным override.

Сначала исследовать:

producer
owner
DOM
cascade
lifecycle

Правильный вопрос:

«какому существующему владельцу должно принадлежать это поведение?»

а не:

«какой selector сделать сильнее, чтобы заработало?»

Краткая памятка

Нельзя

components.css
fixes.css
late override layer
button:not(...) reset
новые visual !important
self render + replaceWith
второй TopBar
второй Dropdown
feature-owned generic Avatar
новый router для локальной задачи

Нужно

один owner
stable page root
local refresh
idempotent cleanup
async disposed guards
shared components
semantic buttons
feature layout/states
минимальная specificity
проверка реального DOM producer

Главный архитектурный принцип

Всегда предпочитать:

один владелец
+
явный contract
+
локальное состояние

вместо:

исторический override
+
случайный source order
+
ещё один fix

Если изменение требует нарушить это правило — сначала остановиться и обосновать архитектурное исключение.