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 Не создавать новый `
` непосредственно в 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. Перед созданием кнопки определить её роль Каждый новый `