Files

1286 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
Не использовать и не добавлять без отдельного архитектурного решения:
```text
React
Vue
Svelte
Solid
Preact
Redux
глобальный state framework
virtual DOM framework
новый router
```
Текущая архитектурная цепочка:
```text
Foundation
App Shell
Shared Components
Semantic UI roles
Features / Pages
```
Направление зависимостей должно сохраняться.
Feature может использовать shared component.
Shared component не должен знать детали конкретного feature.
---
# 2. Зафиксированный baseline
Следующие этапы считаются завершёнными и **не должны откатываться**.
## Stage 1 — App Shell
Shell централизован:
```text
AppShell
├── TopbarSlot
├── ScreenContent
├── ComposerSlot
├── BottomToolbarSlot
├── Global Top Fade
└── Global Bottom Fade
```
Shell управляется через `chrome`.
---
## Stage 2 — Shared TopBar / Dropdown
Используются единые:
```text
js/components/topbar.js
styles/components/topbar.css
js/components/dropdown-menu.js
styles/components/dropdown-menu.css
```
Не создавать альтернативные Header/Menu реализации внутри страниц.
---
## Stage 3 — CSS ownership
Старый:
```text
styles/components.css
```
удалён.
Его нельзя создавать снова.
CSS разделён по владельцам:
```text
styles/components/*
styles/features/*
```
---
## Stage 4 — CSS / Button architecture
Старый глобальный button reset удалён.
Запрещено возвращать архитектуру:
```css
button:not(...):not(...) {
...
}
```
Количество `!important` было уменьшено примерно:
```text
356 → 8
```
Не возвращать specificity wars и late global override layer.
Button styling является opt-in / semantic.
---
## Stage 5 — Page lifecycle
Channel и Thread больше не создают новый instance сами через:
```js
const next = render(...);
current.cleanup?.();
current.replaceWith(next);
```
Текущая модель:
```text
App / Router
mount page instance
stable page root
page.refresh()
local content update
App / Router
page.cleanup()
unmount
```
Эту ownership-модель нельзя откатывать.
---
# 3. Главное правило ownership
Для любого кода сначала определить:
```text
КТО является владельцем?
```
У каждого UI-аспекта должен быть один основной owner.
Примеры:
```text
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 использовать:
```js
chrome.setTopbar(...)
```
Для Composer использовать существующий `chrome` API.
Для shell mode/fades использовать существующий centralized shell contract.
Не создавать внутри ScreenContent собственные:
```text
global header
global fixed toolbar
альтернативный composer slot
копию top fade
копию bottom fade
```
если это уже принадлежит AppShell.
---
# 5. Page lifecycle
## App / Router владеет
```text
mount page root
unmount page root
route change
заменой текущей страницы
shell slot lifecycle
```
## Page владеет
```text
local state
local content
feature listeners
feature timers
feature observers
local refresh
```
---
# 6. Запрещённый self-rerender
Внутри page нельзя делать:
```js
const next = render(...);
screen.cleanup?.();
screen.replaceWith(next);
```
или эквиваленты:
```text
self render + replaceWith
self render + outerHTML
self cleanup + self recreation
recursive page render
```
для обновления текущего route.
Если нужно обновить данные текущей страницы:
```js
screen.refresh()
```
или внутренний:
```js
refresh()
renderBody()
syncView()
```
должен обновить содержимое существующего instance.
Page root при local refresh должен оставаться тем же DOM node.
---
# 7. Stable root
Нормальное поведение:
```js
const rootBefore = screen;
await screen.refresh();
rootBefore === screen;
```
должно оставаться истинным.
При переходе на другой route App создаёт другой root.
Нельзя делать так, чтобы App хранил reference на detached старый root.
---
# 8. Cleanup
Каждая page, создающая lifetime-resources, должна иметь корректный:
```js
screen.cleanup
```
Cleanup должен быть безопасным при повторном вызове.
Предпочтительный pattern:
```js
let disposed = false;
screen.cleanup = () => {
if (disposed) return;
disposed = true;
// cleanup
};
```
---
# 9. Refresh НЕ является cleanup
Нельзя при каждом local refresh выполнять full page cleanup.
Refresh может очищать только refresh-owned content/resources.
Например допустимо:
```text
старый content
старый read tracker
старые refresh timers
```
Но нельзя уничтожать весь page instance и создавать новый.
---
# 10. Async safety
Любая async операция, результат которой обновляет UI, должна учитывать lifetime страницы.
Использовать существующий pattern:
```js
if (disposed) return;
```
и при нескольких последовательных refresh:
```js
const seq = ++refreshSeq;
const result = await load();
if (disposed || seq !== refreshSeq) return;
```
Это предотвращает:
```text
request A
refresh B
B становится актуальным
A завершается позже
A портит новый UI
```
---
# 11. Не мутировать UI после dispose
После:
```js
screen.cleanup()
```
старые async callbacks не должны:
```text
showToast
перерисовывать screen
открывать modal
менять TopBar
менять Composer
добавлять DOM
```
если действие относится к уже закрытой странице.
Перед UI commit проверять active state.
---
# 12. Timers
Lifetime-sensitive timers должны:
1. либо быть безопасными через `isConnected` / `disposed`;
2. либо сохраняться и очищаться.
Например:
```js
const timer = window.setTimeout(...);
```
если timer принадлежит refresh/page lifecycle, должен быть очищаемым.
Не оставлять timeout, который после navigation мутирует detached DOM.
---
# 13. Listeners
Различать:
## Root-owned listener
```js
screen.addEventListener(...)
```
Если root уничтожается вместе со listener, отдельный remove может быть не нужен.
## Global listener
```js
window.addEventListener(...)
document.addEventListener(...)
```
Он обязан иметь понятный cleanup:
```js
window.removeEventListener(...)
```
Не создавать anonymous global listener, который невозможно снять.
---
# 14. Modal ownership
Если page открывает modal, нужно понимать, кому он принадлежит.
При page cleanup нельзя оставлять modal, callback которого замыкает disposed page state.
Для page-owned modal при unmount:
```text
close / destroy / clear
```
его UI и callbacks.
Не удалять чужой modal другой feature.
---
# 15. Dropdown ownership
Не реализовывать заново:
```text
outside click
Escape
portal
backdrop
pressed state
resize/scroll cleanup
```
Использовать общий Dropdown component.
Feature передаёт содержимое/actions.
Dropdown component управляет своим lifecycle.
---
# 16. TopBar ownership
Не создавать новый `<header>` непосредственно в feature.
Использовать общий TopBar.
Feature может передавать:
```text
title
left action
right action
center content
callbacks
```
Но generic geometry/surface TopBar принадлежит shared component.
---
# 17. CSS hierarchy
CSS должен следовать:
```text
main / foundation
layout
app-shell
semantic/shared components
features
```
Нельзя решать cascade problem перестановкой «fix file» в самый конец.
---
# 18. Запрещённые CSS-файлы
Не создавать:
```text
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 должна жить в:
```text
styles/components/*
```
Примеры:
```text
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
В:
```text
styles/features/*
```
допустимы:
```text
feature layout
feature spacing
feature-specific state
feature-specific intentional variant
```
Не допустимы случайные переопределения generic shared component surface.
Например feature может задавать Avatar:
```text
size
placement
margin
```
но не должен случайно заново определять:
```text
generic background
generic frame
generic glass overlay
generic fallback
```
---
# 21. Не добавлять исторические override chains
Запрещён pattern:
```css
.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` по умолчанию запрещены.
Перед добавлением обязательно доказать, почему проблему нельзя решить:
```text
ownership
specificity
source order
state class
semantic role
```
Текущий низкий baseline нельзя ухудшать.
Особенно запрещены:
```css
background: ... !important;
border: ... !important;
color: ... !important;
box-shadow: ... !important;
```
для победы в визуальном cascade.
---
# 23. Button architecture
Старый глобальный reset возвращать запрещено.
Нельзя:
```css
button:not(...) {
background: transparent;
...
}
```
Нельзя:
```css
:root button {
...
}
```
использовать как global visual policy.
---
# 24. Перед созданием кнопки определить её роль
Каждый новый `<button>` должен попасть в одну из категорий:
```text
1. semantic generic button
2. shared component button
3. intentional feature variant
```
---
# 25. Generic semantic button
Если кнопка должна иметь общий neutral Stage-4 surface, использовать существующую semantic role, например:
```text
ui-button
primary-btn
secondary-btn
destructive-btn
ghost-btn
icon-btn
text-btn
shine-btn
```
Не писать отдельный visual reset для одного feature.
---
# 26. Component-owned button
Если button принадлежит:
```text
TopBar
Toolbar
Tabs
Dropdown
EmojiPicker
Call
ScrollToBottom
```
его surface должен принадлежать CSS этого component.
Не добавлять `ui-button` автоматически, если component уже имеет собственный intentional contract.
---
# 27. Composite buttons
Особенно внимательно с:
```html
<button class="card row ...">
```
или:
```text
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.
Минимум:
```text
normal
hover
active
focus-visible
disabled
```
Где применимо:
```text
is-active
is-selected
is-liked
aria-expanded
data-open
```
---
# 29. Button pseudo-elements
При изменении button также проверить:
```text
::before
::after
```
Особенно если используются:
```text
glow
shine
press
overlay
decorative line
```
Не возвращать мёртвые historical pseudo-layers.
---
# 30. `currentColor`
Для icon buttons учитывать:
```css
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 сначала ответить:
```text
Это generic component surface?
или
это только feature layout/state?
```
Если generic — правило, скорее всего, должно находиться в `styles/components/*`.
Если feature-specific — в соответствующем `styles/features/*`.
---
# 33. Не исправлять один экран ломая owner
Если визуальная проблема возникает только в Channel, нельзя автоматически писать:
```css
.channel-screen .shared-component {
...
}
```
Сначала проверить:
* shared component сломан глобально?
* Channel использует неправильный variant?
* feature содержит historical declaration?
* неправильный DOM class?
Исправлять первопричину.
---
# 34. App/router не переписывать локальной задачей
Feature change не должна создавать:
```text
новый router
parallel navigation
собственный page registry
собственный mount manager
```
Навигация выполняется через существующий:
```js
navigate(...)
```
---
# 35. Refresh ≠ navigation
Если пользователь остаётся на той же странице:
```text
like
reply
edit
delete
subscribe
metadata update
```
это обычно local refresh/update.
Не симулировать refresh через:
```js
navigate(currentRoute)
```
если нет специальной архитектурной причины.
---
# 36. Route contract
Не менять без отдельной задачи:
```text
route names
route params
deep links
history semantics
```
Lifecycle refactor не должен менять URL contract.
---
# 37. Backend/API
UI-агент не должен без отдельной задачи менять:
```text
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:
```text
node --check для production JS
relative import audit
CSS parser если CSS менялся
CSS manifest если CSS/imports менялись
!important count если CSS менялся
duplicate-selector audit для затронутого owner
```
---
# 40. Для lifecycle изменений
Дополнительно проверить:
```text
self render + replaceWith отсутствует
root identity сохраняется при refresh
cleanup idempotent
async callback после dispose ничего не мутирует
refresh race защищён
timers/listeners не накапливаются
modal/dropdown не остаются после unmount
```
---
# 41. Для CSS изменений
Дополнительно проверить:
```text
не появился новый global reset
не появился новый late override layer
не вырос !important без причины
shared component не получил feature dependency
feature не стал владельцем generic component surface
```
---
# 42. Для button изменений
Дополнительно проверить:
```text
producer classes
ancestor context
normal
hover
active
focus-visible
disabled
pseudo-elements
currentColor
```
---
# 43. Запрещённые быстрые решения
Если агент собирается сделать что-то из следующего — нужно остановиться и пересмотреть решение:
```text
добавить ещё один !important
создать fixes.css
создать legacy override
поставить CSS-файл последним
добавить button:not(...)
перерисовать page через self replaceWith
создать второй TopBar
создать второй Dropdown
копировать shared component в feature
```
Обычно это признак неправильного ownership.
---
# 44. Search-before-add
Перед созданием:
```text
нового class
нового component
нового helper
нового CSS rule
```
сначала искать аналог по проекту.
Не создавать:
```text
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 по умолчанию означает:
```text
behavior before
=
behavior after
```
Если parity нельзя сохранить — сообщить об этом отдельно.
---
# 47. Комментарии и документация
Комментарии должны объяснять:
```text
WHY
```
а не очевидное:
```text
WHAT
```
Хорошо:
```js
// Ignore stale refresh completion after a newer refresh or page disposal.
```
Плохо:
```js
// Increment variable.
refreshSeq += 1;
```
---
# 48. Не использовать архитектурные документы как мусорный лог
`AGENTS.md` содержит постоянные правила.
Stage-specific audit/report должен быть отдельным документом.
Не добавлять сюда:
```text
временные баги
результаты одного запуска
конкретные debug logs
временные TODO
```
если это не постоянное правило.
---
# 49. Definition of Done для UI-изменения
Изменение считается готовым только если:
```text
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.
Сначала исследовать:
```text
producer
owner
DOM
cascade
lifecycle
```
Правильный вопрос:
```text
«какому существующему владельцу должно принадлежать это поведение?»
```
а не:
```text
«какой selector сделать сильнее, чтобы заработало?»
```
---
# Краткая памятка
## Нельзя
```text
components.css
fixes.css
late override layer
button:not(...) reset
новые visual !important
self render + replaceWith
второй TopBar
второй Dropdown
feature-owned generic Avatar
новый router для локальной задачи
```
## Нужно
```text
один owner
stable page root
local refresh
idempotent cleanup
async disposed guards
shared components
semantic buttons
feature layout/states
минимальная specificity
проверка реального DOM producer
```
# Главный архитектурный принцип
Всегда предпочитать:
```text
один владелец
+
явный contract
+
локальное состояние
```
вместо:
```text
исторический override
+
случайный source order
+
ещё один fix
```
Если изменение требует нарушить это правило — сначала остановиться и обосновать архитектурное исключение.