SHA256
1286 lines
26 KiB
Markdown
1286 lines
26 KiB
Markdown
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
|
||
```
|
||
|
||
Если изменение требует нарушить это правило — сначала остановиться и обосновать архитектурное исключение.
|