Files
microdao-daarion/site/cursor/05_coding_standards/index.html

1098 lines
35 KiB
HTML
Raw 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.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="canonical" href="https://IvanTytar.github.io/microdao-daarion/cursor/05_coding_standards/">
<link rel="icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.5.3, mkdocs-material-9.5.18">
<title>05 — MicroDAO Coding Standards (MVP) - DAARION Documentation</title>
<link rel="stylesheet" href="../../assets/stylesheets/main.66ac8b77.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<script>__md_scope=new URL("../..",location),__md_hash=e=>[...e].reduce((e,_)=>(e<<5)-e+_.charCodeAt(0),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#05-microdao-coding-standards-mvp" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="../.." title="DAARION Documentation" class="md-header__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3V6m0 5h18v2H3v-2m0 5h18v2H3v-2Z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
DAARION Documentation
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
05 — MicroDAO Coding Standards (MVP)
</span>
</div>
</div>
</div>
<script>var media,input,key,value,palette=__md_get("__palette");if(palette&&palette.color){"(prefers-color-scheme)"===palette.color.media&&(media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']"),palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent"));for([key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.516 6.516 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5Z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11h12Z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="../.." title="DAARION Documentation" class="md-nav__button md-logo" aria-label="DAARION Documentation" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54Z"/></svg>
</a>
DAARION Documentation
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../public/" class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/getting-started/" class="md-nav__link">
<span class="md-ellipsis">
Getting Started
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/architecture-overview/" class="md-nav__link">
<span class="md-ellipsis">
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../public/daiS_daos_overview/" class="md-nav__link">
<span class="md-ellipsis">
DAIS & DAOS
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5" >
<label class="md-nav__link" for="__nav_5" id="__nav_5_label" tabindex="">
<span class="md-ellipsis">
Internal
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_5_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5">
<span class="md-nav__icon md-icon"></span>
Internal
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_1" >
<label class="md-nav__link" for="__nav_5_1" id="__nav_5_1_label" tabindex="0">
<span class="md-ellipsis">
Infra
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_1_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_1">
<span class="md-nav__icon md-icon"></span>
Infra
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/infra/INFRA_AUTOMATION_PACK_V1/" class="md-nav__link">
<span class="md-ellipsis">
Infra Automation Pack v1
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/monitoring_overview/" class="md-nav__link">
<span class="md-ellipsis">
Monitoring Overview
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/infra/nodes_registry_v0/" class="md-nav__link">
<span class="md-ellipsis">
Nodes Registry v0
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_5_2" >
<label class="md-nav__link" for="__nav_5_2" id="__nav_5_2_label" tabindex="0">
<span class="md-ellipsis">
Specs
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="2" aria-labelledby="__nav_5_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_5_2">
<span class="md-nav__icon md-icon"></span>
Specs
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../../internal/specs/matrix_presence_aggregator/" class="md-nav__link">
<span class="md-ellipsis">
Matrix Presence Aggregator
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/city_map_spec/" class="md-nav__link">
<span class="md-ellipsis">
City Map Spec
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../../internal/specs/node_join_protocol_draft/" class="md-nav__link">
<span class="md-ellipsis">
Node Join Protocol (Draft)
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1" class="md-nav__link">
<span class="md-ellipsis">
1. Загальні принципи
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#2" class="md-nav__link">
<span class="md-ellipsis">
2. Архітектура проєкту
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#3-typescript" class="md-nav__link">
<span class="md-ellipsis">
3. TypeScript Правила
</span>
</a>
<nav class="md-nav" aria-label="3. TypeScript Правила">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#31" class="md-nav__link">
<span class="md-ellipsis">
3.1. Строгий режим
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#32" class="md-nav__link">
<span class="md-ellipsis">
3.2. Заборонено
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#33-api-" class="md-nav__link">
<span class="md-ellipsis">
3.3. API-типи
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#4-react-query-network-layer" class="md-nav__link">
<span class="md-ellipsis">
4. React Query (network layer)
</span>
</a>
<nav class="md-nav" aria-label="4. React Query (network layer)">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#41-fetch-wrapper" class="md-nav__link">
<span class="md-ellipsis">
4.1. Fetch wrapper
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#42-query-keys" class="md-nav__link">
<span class="md-ellipsis">
4.2. Query Keys
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#5" class="md-nav__link">
<span class="md-ellipsis">
5. Стандарти компонентів
</span>
</a>
<nav class="md-nav" aria-label="5. Стандарти компонентів">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#51" class="md-nav__link">
<span class="md-ellipsis">
5.1. Іменування
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#52" class="md-nav__link">
<span class="md-ellipsis">
5.2. Компонент повинен мати:
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#6" class="md-nav__link">
<span class="md-ellipsis">
6. Обробка помилок
</span>
</a>
<nav class="md-nav" aria-label="6. Обробка помилок">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#61-toastnotification" class="md-nav__link">
<span class="md-ellipsis">
6.1. Toast/notification
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#62-errorboundary" class="md-nav__link">
<span class="md-ellipsis">
6.2. ErrorBoundary
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#63-retry-policy" class="md-nav__link">
<span class="md-ellipsis">
6.3. Retry policy
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#7-i18n" class="md-nav__link">
<span class="md-ellipsis">
7. i18n стандарти
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#8-ui" class="md-nav__link">
<span class="md-ellipsis">
8. UI та дизайн
</span>
</a>
<nav class="md-nav" aria-label="8. UI та дизайн">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#81" class="md-nav__link">
<span class="md-ellipsis">
8.1. Кольори
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#82" class="md-nav__link">
<span class="md-ellipsis">
8.2. Типографіка
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#83" class="md-nav__link">
<span class="md-ellipsis">
8.3. Контрасти
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#9-websockets" class="md-nav__link">
<span class="md-ellipsis">
9. Робота з WebSockets
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#10-mvp" class="md-nav__link">
<span class="md-ellipsis">
10. Обмеження для MVP
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#11-cursor" class="md-nav__link">
<span class="md-ellipsis">
11. Патерни, які Cursor повинен дотримуватися
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#12-cursor" class="md-nav__link">
<span class="md-ellipsis">
12. Приклад робочого промта для Cursor
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#13" class="md-nav__link">
<span class="md-ellipsis">
13. Мета документа
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="05-microdao-coding-standards-mvp">05 — MicroDAO Coding Standards (MVP)<a class="headerlink" href="#05-microdao-coding-standards-mvp" title="Permanent link">&para;</a></h1>
<p>Цей документ визначає мінімальні стандарти коду, яким повинен відповідати фронтенд MicroDAO.
Його мета — забезпечити якість, узгодженість і стабільність розробки, особливо при використанні Cursor.</p>
<h2 id="1">1. Загальні принципи<a class="headerlink" href="#1" title="Permanent link">&para;</a></h2>
<ol>
<li>
<p><strong>Тільки TypeScript.</strong>
Заборонено <code>any</code> та <code>unknown</code>, окрім явно позначених місць.</p>
</li>
<li>
<p><strong>Компоненти — функціональні.</strong>
Не використовувати класові компоненти.</p>
</li>
<li>
<p><strong>Стан — мінімалістичний.</strong>
Локальний стан → React useState
Глобальний короткочасний стан → Context або Zustand
Дані з API → React Query</p>
</li>
<li>
<p><strong>Ясність важливіша за магію.</strong>
Прості компоненти, зрозумілі хуки, передбачувані сторінки.</p>
</li>
<li>
<p><strong>Принцип: один файл — одна відповідальність.</strong></p>
</li>
</ol>
<h2 id="2">2. Архітектура проєкту<a class="headerlink" href="#2" title="Permanent link">&para;</a></h2>
<div class="codehilite"><pre><span></span><code>src/
api/ // Typed API clients
components/ // UI components (buttons, inputs, modals)
features/ // Business-level modules (chat, onboarding, agents)
hooks/ // Reusable react hooks
layout/ // Application layout
routes/ // Route definitions
store/ // Zustand stores (optional)
styles/ // Global CSS/tokens
utils/ // Formatting, validation
</code></pre></div>
<ul>
<li><code>features/*</code> містять логіку конкретних модулів.</li>
<li><code>components/*</code> — лише dumb UI-компоненти (без бізнес-логіки).</li>
</ul>
<h2 id="3-typescript">3. TypeScript Правила<a class="headerlink" href="#3-typescript" title="Permanent link">&para;</a></h2>
<h3 id="31">3.1. Строгий режим<a class="headerlink" href="#31" title="Permanent link">&para;</a></h3>
<p>У <code>tsconfig.json</code>:</p>
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;compilerOptions&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nt">&quot;strict&quot;</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
<span class="w"> </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h3 id="32">3.2. Заборонено<a class="headerlink" href="#32" title="Permanent link">&para;</a></h3>
<ul>
<li><code>any</code></li>
<li><code>!</code> non-null assertion (за винятком рідкісних випадків)</li>
<li>глобальний mutable state</li>
</ul>
<h3 id="33-api-">3.3. API-типи<a class="headerlink" href="#33-api-" title="Permanent link">&para;</a></h3>
<ul>
<li>Генеруємо типи з API Snapshot / OpenAPI.</li>
<li>Типи для відповідей зберігаються в <code>src/api/types.ts</code>.</li>
</ul>
<h2 id="4-react-query-network-layer">4. React Query (network layer)<a class="headerlink" href="#4-react-query-network-layer" title="Permanent link">&para;</a></h2>
<h3 id="41-fetch-wrapper">4.1. Fetch wrapper<a class="headerlink" href="#41-fetch-wrapper" title="Permanent link">&para;</a></h3>
<p>Один універсальний wrapper:</p>
<div class="codehilite"><pre><span></span><code><span class="k">export</span><span class="w"> </span><span class="k">async</span><span class="w"> </span><span class="kd">function</span><span class="w"> </span><span class="nx">api</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">path</span><span class="o">:</span><span class="w"> </span><span class="kt">string</span><span class="p">,</span><span class="w"> </span><span class="nx">options?</span><span class="o">:</span><span class="w"> </span><span class="kt">RequestInit</span><span class="p">)</span><span class="o">:</span><span class="w"> </span><span class="nb">Promise</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">res</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">fetch</span><span class="p">(</span><span class="sb">`/v1</span><span class="si">${</span><span class="nx">path</span><span class="si">}</span><span class="sb">`</span><span class="p">,</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="nx">headers</span><span class="o">:</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="s2">&quot;Content-Type&quot;</span><span class="o">:</span><span class="w"> </span><span class="s2">&quot;application/json&quot;</span><span class="p">,</span>
<span class="w"> </span><span class="p">...</span><span class="nx">options</span><span class="o">?</span><span class="p">.</span><span class="nx">headers</span>
<span class="w"> </span><span class="p">},</span>
<span class="w"> </span><span class="p">...</span><span class="nx">options</span>
<span class="w"> </span><span class="p">});</span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">res</span><span class="p">.</span><span class="nx">ok</span><span class="p">)</span><span class="w"> </span><span class="p">{</span>
<span class="w"> </span><span class="kd">const</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">await</span><span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">().</span><span class="k">catch</span><span class="p">(()</span><span class="w"> </span><span class="p">=&gt;</span><span class="w"> </span><span class="p">({}));</span>
<span class="w"> </span><span class="k">throw</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="ne">Error</span><span class="p">(</span><span class="nx">err</span><span class="p">.</span><span class="nx">message</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="sb">`Request failed: </span><span class="si">${</span><span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
<span class="w"> </span><span class="p">}</span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div>
<h3 id="42-query-keys">4.2. Query Keys<a class="headerlink" href="#42-query-keys" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="k">[&quot;teams&quot;]</span>
<span class="k">[&quot;teams&quot;, teamId]</span>
<span class="k">[&quot;channels&quot;, teamId]</span>
<span class="k">[&quot;messages&quot;, channelId]</span>
<span class="k">[&quot;followups&quot;, teamId]</span>
<span class="k">[&quot;projects&quot;, teamId]</span>
</code></pre></div>
<h2 id="5">5. Стандарти компонентів<a class="headerlink" href="#5" title="Permanent link">&para;</a></h2>
<h3 id="51">5.1. Іменування<a class="headerlink" href="#51" title="Permanent link">&para;</a></h3>
<ul>
<li>Компоненти: <code>PascalCase</code></li>
<li>Хуки: <code>useCamelCase</code></li>
<li>Файли: <code>camel-case.tsx</code></li>
<li>Папки: <code>kebab-case</code></li>
</ul>
<h3 id="52">5.2. Компонент повинен мати:<a class="headerlink" href="#52" title="Permanent link">&para;</a></h3>
<ul>
<li>Чіткий props-інтерфейс:</li>
</ul>
<p><code>ts
interface MyCompProps {
title: string;
onClick: () =&gt; void;
}</code>
* Внутрішній стан не змішується з зовнішнім API-станом.
* Міжкомпонентна логіка виноситься в хуки (наприклад: <code>useMessages(channelId)</code>).</p>
<h2 id="6">6. Обробка помилок<a class="headerlink" href="#6" title="Permanent link">&para;</a></h2>
<h3 id="61-toastnotification">6.1. Toast/notification<a class="headerlink" href="#61-toastnotification" title="Permanent link">&para;</a></h3>
<p>Помилка API → коротке повідомлення:</p>
<blockquote>
<p>"Не вдалося виконати дію. Спробуйте ще раз."</p>
</blockquote>
<h3 id="62-errorboundary">6.2. ErrorBoundary<a class="headerlink" href="#62-errorboundary" title="Permanent link">&para;</a></h3>
<p>Окрема сторінка помилки для критичних збоїв.</p>
<h3 id="63-retry-policy">6.3. Retry policy<a class="headerlink" href="#63-retry-policy" title="Permanent link">&para;</a></h3>
<p>React Query retry: <code>retry: 1</code> для GET-запитів
POST — без retry.</p>
<h2 id="7-i18n">7. i18n стандарти<a class="headerlink" href="#7-i18n" title="Permanent link">&para;</a></h2>
<p>Всі тексти повинні бути в словнику:</p>
<div class="codehilite"><pre><span></span><code>src/i18n/uk.json
src/i18n/en.json
</code></pre></div>
<p>Формат ключів:</p>
<div class="codehilite"><pre><span></span><code>onboarding.welcome_title
onboarding.next
chat.send
chat.input_placeholder
followup.create
</code></pre></div>
<p>Форсувати одразу правильну структуру.</p>
<h2 id="8-ui">8. UI та дизайн<a class="headerlink" href="#8-ui" title="Permanent link">&para;</a></h2>
<h3 id="81">8.1. Кольори<a class="headerlink" href="#81" title="Permanent link">&para;</a></h3>
<div class="codehilite"><pre><span></span><code><span class="nt">--primary</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">3F51F5</span><span class="o">;</span>
<span class="nt">--success</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">43A047</span><span class="o">;</span>
<span class="nt">--error</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">E53935</span><span class="o">;</span>
<span class="nt">--gray-100</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">F8F9FA</span><span class="o">;</span>
<span class="nt">--gray-200</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">ECEFF1</span><span class="o">;</span>
<span class="nt">--gray-800</span><span class="o">:</span><span class="w"> </span><span class="p">#</span><span class="nn">263238</span><span class="o">;</span>
</code></pre></div>
<h3 id="82">8.2. Типографіка<a class="headerlink" href="#82" title="Permanent link">&para;</a></h3>
<ul>
<li>System font stack:
<code>"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto</code></li>
</ul>
<h3 id="83">8.3. Контрасти<a class="headerlink" href="#83" title="Permanent link">&para;</a></h3>
<p>Всі текстові елементи повинні відповідати WCAG AA (axe test).</p>
<h2 id="9-websockets">9. Робота з WebSockets<a class="headerlink" href="#9-websockets" title="Permanent link">&para;</a></h2>
<ul>
<li>Використовуємо один хук: <code>useChannelStream(channelId)</code>.</li>
<li>WS підключається коли відкрито чат.</li>
<li>
<p>Події:</p>
</li>
<li>
<p><code>message.created</code></p>
</li>
<li><code>message.updated</code></li>
</ul>
<p>Не зберігати WS-стан у глобальному store.</p>
<h2 id="10-mvp">10. Обмеження для MVP<a class="headerlink" href="#10-mvp" title="Permanent link">&para;</a></h2>
<p>Що треба <strong>вимкнути</strong> у коді, щоб не перевантажити ранніх користувачів:</p>
<ul>
<li>Без drag'n'drop для файлів.</li>
<li>Без реакцій (emoji).</li>
<li>Без WYSIWYG редактора.</li>
<li>Без Co-Memory (файли/документи), лише stub.</li>
<li>Без granular RBAC.</li>
</ul>
<h2 id="11-cursor">11. Патерни, які Cursor повинен дотримуватися<a class="headerlink" href="#11-cursor" title="Permanent link">&para;</a></h2>
<ol>
<li><strong>Atomic commits</strong>: 1 Фіча → 1 commit.</li>
<li><strong>File-oriented prompts</strong>: кожен запит до Cursor повинен містити список файлів для зміни.</li>
<li><strong>Не переписувати цілі модулі</strong>, якщо не потрібно.</li>
<li><strong>Перевіряти типи</strong> перед генерацією нового коду.</li>
<li><strong>Не вигадувати API</strong> — брати тільки з <code>03_api_core_snapshot.md</code>.</li>
</ol>
<h2 id="12-cursor">12. Приклад робочого промта для Cursor<a class="headerlink" href="#12-cursor" title="Permanent link">&para;</a></h2>
<div class="codehilite"><pre><span></span><code>You are a senior React/TS engineer.
Implement Step 2 of the onboarding flow (/onboarding).
Specs:
<span class="k">-</span> design from 04_ui_ux_onboarding_chat.md
<span class="k">-</span> API from 03_api_core_snapshot.md
<span class="k">-</span> coding standards from 05_coding_standards.md
Please output:
<span class="k">-</span> list of files to modify
<span class="k">-</span> code diff
</code></pre></div>
<h2 id="13">13. Мета документа<a class="headerlink" href="#13" title="Permanent link">&para;</a></h2>
<p>Цей файл — "правила дорожнього руху" для команди і Cursor.</p>
<p>Він гарантує:</p>
<ul>
<li>узгоджений стиль,</li>
<li>передбачуваний код,</li>
<li>мінімум помилок,</li>
<li>легку підтримку,</li>
<li>зрозумілість структури для нових девелоперів.</li>
</ul>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"base": "../..", "features": ["navigation.sections", "navigation.instant", "content.code.copy"], "search": "../../assets/javascripts/workers/search.b8dbb3d2.min.js", "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}}</script>
<script src="../../assets/javascripts/bundle.3220b9d7.min.js"></script>
</body>
</html>