Цель: последовательность фаз разработки ARNO, где после каждой итерации есть visible artifact — можно посмотреть, потрогать, продемонстрировать.
Принципы
- Vertical slicing > horizontal layers. Каждая фаза = full-stack vertical через текущую feature.
- Demo-driven. Каждая фаза заканчивается чем-то что можно открыть в браузере и показать.
- Throwaway acceptable в spike phases (1-7). Mocks → real services постепенно.
- Cumulative. Каждая фаза строит на предыдущей.
- AlfaBank как первый customer / testbed. Реальные MD + TSX уже существуют в
Desktop/Claude/AlfaBank/. Dogfood с самого начала. - Master spec (
_index.md) — canonical reference для tech decisions. Workflow здесь — последовательность, не дублирует архитектурные решения.
Phase overview
| # | Phase | Visible result | Days | Status |
|---|---|---|---|---|
| 0 | Foundation skeleton | Blank Next.js на *.pages.dev | 1 | 🟢 done — https://arno-ijr.pages.dev (opens in a new tab) |
| 1 | Static workflow canvas | Mock граф из 5 экранов с рёбрами | 2 | 🟢 done — https://arno-ijr.pages.dev/app (opens in a new tab) |
| 2 | Editable canvas (local) | Создание/перемещение экранов и рёбер | 2 | 🟢 done — https://arno-ijr.pages.dev/app (opens in a new tab) |
| 3 | Screen editor (local) | Drop компонентов из mock-library, configure props | 3 | 🟢 done — https://arno-ijr.pages.dev/app/screen (opens in a new tab) |
| 4 | Local persistence | IndexedDB — работа переживает reload | 1 | 🟢 done — auto-saves to arno-local DB |
| 5 | Real DS from AlfaBank | Library показывает реальные компоненты из MD | 3 | 🟢 done — GitHub Contents API → raw MD → parse (repo ChrisPianof/-) |
| 6 | Preview mode = live render | iframe с настоящим React-рендером AlfaBank Button | 4 | 🟢 done — bundle on chrispianof.github.io + sandboxed iframe |
| 7 | Interactivity (clickable prototype) 🎯 | Кликабельный prototype с реальными компонентами | 2 | 🟢 done — + picker + navigations + history stack |
| — | Spike→steady refactor | Cleanup throwaway, prep prod-stack | 2 | 🟢 done — 3 packages, 18 unit tests, CI, 4 ADRs |
| 8 | Cloud persistence | Neon + Hono + Drizzle, work cloud-backed | 3 | 🟢 done — https://arno-api.vadimpianof.workers.dev (opens in a new tab) |
| 9 | Auth + projects | GitHub OAuth, projects scoped к user | 2 | 🟢 done — backend OAuth + per-user projects (ADR-0006) |
| 10 | Sharing | Share-link, anonymous viewer | 2 | 🟢 done — /share + /share/preview public read-only |
| 11 | Real-time collab | Liveblocks + Yjs, multi-user live | 3 | 🟢 done — workflow в Yjs Storage, multi-tab sync |
| 12 | MD smart editor | Fenced blocks, versions, conflict UI | 5 | 🟢 done — /app/component editor + versions trigger-purged |
| 13 | Sync с репой | Session branches, auto-PR, GitHub App | 4 | |
| 14 | Drift detection + CI | arno-check.yml, 5 statuses в UI | 3 | |
| 15 | Onboarding flow | Full first-run UX | 3 | |
| 16 | Production readiness | Observability, a11y, probes, launch checklist | 5 |
Итого ≈ 50 рабочих дней (~10 недель solo dev).
Demo milestones
- End of Phase 2: играешься с canvas, UI чувствуется
- End of Phase 7: 🎯 clickable prototype с AlfaBank-компонентами — первый WOW
- End of Phase 10: можешь шарить ссылку любому
- End of Phase 11: multi-user real-time
- End of Phase 13: полный git-loop с твоими репами
- End of Phase 16: launch-ready product
Phase 0 — Foundation skeleton
Goal: инфраструктурная база, на которой строим всё остальное. Никакой product-логики.
Visible result: открываешь https://arno-XXX.pages.dev — видишь blank page с надписью "ARNO — coming soon".
Technical scope:
- Monorepo init:
pnpm+ Turborepo + base configs (TS, ESLint, Prettier) apps/web/— Next.js 14 App Router skeleton (один pageHome)tools/eslint-config/,tools/tsconfig/базовые- Cloudflare Pages deploy через
wranglerили git integration - GitHub repository created + first commit + remote push
Dependencies: none.
Throwaway-or-keep: keep — это steady-state foundation.
Acceptance criteria:
-
pnpm devзапускает Next.js локально на :3000 (verified viapnpm buildstatic export —out/index.htmlсодержит "ARNO", "Cloud design editor", "Coming soon";pnpm lint/pnpm typecheckclean) - Push к main → auto-deploy на Cloudflare Pages (commit b94d8e9, production deployment success)
- Public URL открывается и показывает "Hello ARNO" — https://arno-ijr.pages.dev (opens in a new tab)
Implementation notes (2026-05-20):
- Static export (
output: "export") выбран как простейший путь для Phase 0. Переход на edge adapter (@cloudflare/next-on-pagesили Workers Static Assets + OpenNext) — Phase 8-9 когда появятся Auth/API. - Cloudflare flow: новый объединённый UI "Workers & Pages" заводит проекты через Worker-like UI, но всё ещё требует Pages-style
wrangler.jsoncс полемpages_build_output_dir. Documented вdocs/cloudflare-pages.md.
Phase 1 — Static workflow canvas
Goal: визуально понять что мы строим. Дать каждому (себе тоже) представление product look-and-feel.
Visible result: на /app route видишь mock workflow — 5 хардкодных экранов (Login, Onboarding, Home, Profile, Settings), соединённых стрелками. Static SVG/canvas.
Technical scope:
apps/web/app/app/page.tsx— editor route- Установить react-flow (или подобную lib) — workflow visualization
- Hardcoded data:
const screens = [ { id: 'login', name: 'Login', position: {x: 0, y: 0} }, { id: 'onboarding', name: 'Onboarding', position: {x: 200, y: 0} }, ... ] const edges = [ { from: 'login', to: 'onboarding' }, ... ] - Sidebar layout (Components / Workflow tabs — статичные)
- No interaction — пока visual только
Dependencies: Phase 0.
Throwaway-or-keep: mock data throwaway, react-flow integration keep.
Acceptance criteria:
- Открываешь
/app→ видишь workflow граф (https://arno-ijr.pages.dev/app/ (opens in a new tab), HTTP 200) - Screens расположены и читаемы (5 nodes: Login, Onboarding, Home, Profile, Settings; verified в SSR HTML)
- Рёбра соединяют screens (4 edges, Login→Onboarding→Home→{Profile, Settings}, animated на первых двух)
- Sidebar присутствует с tabs (Components / Workflow, interactive switch)
Implementation note (2026-05-20): lib = @xyflow/react v12.10.2 (react-flow преемник, поддерживает Next.js App Router + SSR). Custom screen node, dark theme через CSS vars, dots background, nodesDraggable=false для read-only Phase 1. Bundle: /app 55kB, First Load 142kB (well under 4MB CF budget).
Phase 2 — Editable canvas (local state)
Goal: превратить просмотр в редактор. Можно играться с canvas, ощутить UX.
Visible result: кнопка "+ Screen" добавляет новый экран. Drag-and-drop двигает экраны. Drag from screen edge → new edge. Right-click → delete. State в React, обновление страницы стирает.
Technical scope:
- React-flow customization для add/delete/move
- Local React state (
useStateилиuseReducer) для screens + edges - UI controls: + screen button, delete, edit name (inline)
- Edge creation by dragging from screen handles
Dependencies: Phase 1.
Throwaway-or-keep: local state — throwaway (replaced в Phase 4). UI controls keep.
Acceptance criteria:
- Создаю новый экран кнопкой — "+ Screen" в floating toolbar (top-left)
- Перетаскиваю экраны мышью — default xyflow drag
- Рисую edge от одного экрана к другому — source/target Handles (фиолетовые точки)
- Удаляю экран и edges (cleanup) — right-click context menu OR Backspace/Delete; onNodesDelete фильтрует связанные edges
- Переименовываю экран inline — double-click на title → input → Enter/blur save, Esc cancel
Implementation notes (2026-05-20):
- State:
useNodesState+useEdgesStateиз @xyflow/react (local React state, throwaway → IndexedDB в Phase 4). onRenamecallback инжектится черезnodesWithHandlersmemo (xyflow требует immutable data refs).onKeyDownCapture stopPropagationна input rename — критично, иначе Backspace во время редактирования удаляет ноду через ReactFlow'sdeleteKeyCode.ReactFlowProviderwrap — для возможности использоватьuseReactFlowвнутри (понадобится в Phase 3+).
Phase 3 — Screen editor (local)
Goal: ввести понятие composition. Drill into screen, добавлять компоненты, настраивать пропы.
Visible result: клик на экран в workflow → drill in → видишь "screen edit mode". Сайдбар с mock-library: Button, Input, BgPlate. Drag-and-drop в screen body. Клик на компонент → right panel с пропами (label, variant, size). Edit пропы — обновляется визуально.
Technical scope:
- Routing:
/app/screen/:idдля drill-in - Mock component library:
const COMPONENTS = [ { id: 'button', name: 'Button', props: [ { name: 'label', type: 'string', default: 'Click' }, { name: 'variant', type: 'enum', values: ['primary', 'secondary'] } ]}, { id: 'input', name: 'Input', props: [...] }, { id: 'bgplate', name: 'BgPlate', props: [...], slots: ['header', 'body'] }, ] - Drop zone компонент → composition tree
- Right panel: dynamic form по props schema
- Render: schematic (labeled boxes), не реальный React
Dependencies: Phase 2.
Throwaway-or-keep: mock library throwaway (replaced в Phase 5). Drop UX, props panel UX, composition tree model — keep.
Acceptance criteria:
- Drill in в screen работает — double-click на body экрана в
/app→/app/screen?id=<id>&name=<name> - Sidebar mock-library показывает 3+ компонента — 4: Button, Input, Title, BgPlate
- Drag компонент в screen body → появляется — HTML5 DnD, mime
application/x-arno-component - Клик на инстанс → right panel с пропами —
PropsPanelс dynamic form по schema - Изменение пропа отражается на canvas (labeled box updates) —
useSyncExternalStoreподписан, instant update - Nested composition: BgPlate → внутри Button работает —
Slotпринимает drops вchildren
Implementation notes (2026-05-20):
- Route
/app/screen— single static file, screenId+name из query (useSearchParams+Suspenseboundary, обязательно для Next.js static export). Эквивалент[id]dynamic param без backend. - State:
composition-store.ts— module-level singleton + React 18useSyncExternalStore. Persists между/app↔/app/screennavigation (same module instance), теряется на reload. Phase 4 заменим на IndexedDB. - Workflow rename UX vs drill-in: double-click на title узла → rename (через
stopPropagationвScreenNode), double-click на body узла → drill-in (черезonNodeDoubleClickвReactFlow). - DnD: native HTML5 (
draggable,onDragStart,onDragOver,onDrop). Без библиотек. - Workflow и composition пока — два независимых singleton'а. Workflow store сделаем в Phase 4 вместе с IndexedDB.
Phase 4 — Local persistence
Goal: работа не должна теряться при reload. Можно вернуться к проекту.
Visible result: делаешь изменения → закрываешь вкладку → открываешь → всё на месте.
Technical scope:
- IndexedDB через библиотеку (idb, dexie, или native)
- Auto-save на каждое change (debounce 500ms)
- Load on mount
- "Reset project" debug button (нужно для testing)
Dependencies: Phase 3.
Throwaway-or-keep: IndexedDB — throwaway (replaced cloud persistence Phase 8). Auto-save pattern — keep.
Acceptance criteria:
- Изменения сохраняются автоматически —
subscribeна оба store,debounce 500ms→saveWorkflow/saveComposition - Reload → state восстанавливается —
PersistenceLoaderна mount грузит из IDB, hydrates stores до first render - Closing/opening browser preserves work — IndexedDB
arno-localpersists between sessions (browser-controlled, не sessionStorage) - Reset button очищает state — Sidebar footer → confirm →
resetAll()→window.location.reload()
Implementation notes (2026-05-20):
- Lib:
idb@8(~7KB, type-safe IndexedDB wrapper). - IDB schema: DB
arno-localv1, 2 object stores —workflow(keyPathid, единственная записьid="default"),compositions(keyPathscreenId). - Per-screen save delivery:
compositionStore.subscribeChanges()сравнивает prev/next snapshot и доставляет только изменившиесяScreenComposition— избегаем write всего dataset при изменении одного screen. - Workflow store extracted из WorkflowCanvas useNodesState/useEdgesState → module singleton
workflow-store.ts(analogous to composition-store). Это также фиксит implicit Phase 3 issue: state не терялся бы между navigation workflow ↔ screen уже теоретически, но при добавлении IDB load on mount был бы flash. onRenamecallback (non-serializable) чистится перед IDB write черезnodes.map(n => ({...n, data: {label, description}})).- PersistenceLoader рендерит loading shell до hydration — никакого FOUC с initialScreens перед загрузкой пользовательских данных.
Phase 5 — Real DS from AlfaBank
Goal: заменить mock-library реальными компонентами из существующей design system. Доказать что ARNO работает с настоящими репами.
Visible result: library показывает реальные AlfaBank компоненты (TitleView, ButtonsGroup, etc.) — с реальными именами, props из их MD-спек. Drop в screen → labeled boxes с реальными именами.
Technical scope:
- AlfaBank repo используется как public GitHub repo (или local file fetch для MVP)
- Fetch MD files через GitHub API public endpoint (no auth для public repo):
https://api.github.com/repos/{owner}/{repo}/contents/{path} - Parse frontmatter (gray-matter library)
- Parse fenced structural blocks v1 (
<!-- arno:props v1 -->) - Component spec extraction: id, name, props, events
- Replace mock COMPONENTS array с real fetched data
- Cache в IndexedDB (offline + skip rate limit)
Dependencies: Phase 4. AlfaBank Design_system/ MD files должны быть в правильном формате (если ещё не — add id к frontmatter, оборачивать props в fenced v1 markers).
Throwaway-or-keep: fetch logic — partially throwaway (real backend в Phase 8). MD parser — keep, exported в packages/editor/ later.
Pre-work: добавить в AlfaBank Design_system хотя бы 3 MD-файла с правильным форматом:
---
id: cmp-btn-001
name: Button
---
<!-- arno:props v1 -->
- id: p_label
name: label
type: string
textEditable: true
- id: p_variant
name: variant
type: enum
values: [primary, secondary]
<!-- /arno:props v1 -->Acceptance criteria:
- ARNO fetches MD files — через
/design-system/*.md(snapshot, не GitHub API; см. notes) - Frontmatter parsed correctly (id, name) —
md-parser.tscustom YAML - Fenced structural blocks parsed (props, types, values) —
<!-- arno:props v1 -->+<!-- arno:slots v1 --> - Library sidebar показывает 4+ реальных компонента — ButtonDesktop, InputDesktop, TitleView, BackgroundPlate
- Drop AlfaBank Button в screen → labeled box с правильным именем
- Props panel показывает реальные пропы из MD (view, size, label, placeholder, type, block, position, heading, subtitle...)
- Cache работает (offline mode shows last fetched) — IDB
librarystore, cache-first → network update
Implementation notes (2026-05-20):
- GitHub Contents API + raw URLs — AlfaBank опубликован как public repo
ChrisPianof/-(нормализованное GitHub имя из исходного "а"). Loader делает 1 запрос/repos/.../contents/Design_systemдля listing → параллельно тянет MD черезraw.githubusercontent.com(не учитывается в API rate limit). CORS*на обоих endpoints. Раньше был local snapshotpublic/design-system/— удалён после переключения. - CF Pages edge cache gotcha: удалённые
public/assets живут на edge ещё 7 дней (cache-control: public, s-maxage=604800дефолт). URL/design-system/Buttons.mdвозвращает 200 пока кэш не expire, но ARNO loader его не использует. При необходимости — purge через CF API. - MD augmentation в AlfaBank: 4 файла (Buttons, Input, TitleView, BackgroundPlate) получили
---id: cmp-..._name: ...---frontmatter + fenced<!-- arno:props v1 -->блок с пропами из существующих "## Пропы" таблиц. Human-readable docs остались нетронутыми ниже. Закоммичено в AlfaBank repo. - Кастомный YAML parser (md-parser.ts) — без gray-matter (~30KB сэкономлено). Поддерживает frontmatter key:value, fenced
- id: ... name: ... values: [...]lists, quoted strings, inline arrays. - Cache-first loading в design-system-loader: instant hydrate из IDB (если есть), параллельный network fetch → update + persist. Network fail → cache остаётся валидным. Cold start без cache → fallback library (5 mock компонентов из Phase 3).
- Library source badge в panel footer (
AlfaBank design-system/IndexedDB cache/fallback (mock)) — visible signal что именно загружено. - Phase 13 заменит local fetch на GitHub API (когда AlfaBank на GitHub). Architecture готова:
loadDesignSystemинкапсулирует source, остальной код работает черезuseLibrary()hook.
Phase 6 — Preview mode = live render
Goal: killer feature. Реальный React Button с реальными стилями появляется в preview. Главный wow-эффект.
Visible result: в screen edit mode — кнопка "Preview". Клик → modal/route opens → видишь экран отрендеренный реальными AlfaBank-компонентами с реальным CSS. Button выглядит как настоящая, Input работает.
Technical scope:
Pre-work в AlfaBank:
- Создать
arno.entry.tsxв AlfaBank root:// Listens к postMessage events from ARNO iframe // Renders component by id с переданными props window.addEventListener('message', (e) => { if (e.data.type === 'render') { const { component, props } = e.data ReactDOM.render(<Components[component] {...props} />, root) } }) - GitHub Action
.github/workflows/arno-build.yml— собирает bundle, publishes к gh-pages - Enable GitHub Pages на AlfaBank repo (settings)
- Verify bundle accessible:
https://{owner}.github.io/AlfaBank/arno-bundle.js
В ARNO:
- Preview route
/app/screen/:id/preview - Sandboxed iframe loads AlfaBank bundle URL
- postMessage protocol: ARNO → iframe
{type: 'render', component, props} - Composition tree → recursive postMessage rendering
- CSP headers для iframe sandbox
Dependencies: Phase 5.
Throwaway-or-keep: keep — это core render adapter protocol, used forever.
Acceptance criteria:
- AlfaBank bundle собран и publishes на GitHub Pages — https://chrispianof.github.io/-/arno-bundle.js (opens in a new tab) (726KB raw / 193KB gzip, IIFE, CSS injected via JS)
- ARNO loads bundle в sandboxed iframe —
<iframe sandbox="allow-scripts">с inlinesrcdoc(нет allow-same-origin → нет доступа к parent cookies/localStorage) - Preview route рендерит реальный AlfaBank Button с реальным styling — ButtonDesktop / InputDesktop из
@alfalab/core-components+ corp.css theme - Изменение пропа в ARNO → iframe re-renders —
useEffectподписан на composition store, постит новый tree на каждое изменение - Nested composition (BgPlate → Button) рендерится правильно —
renderNodeрекурсивный, slotchildren - Iframe CSP не утекает —
sandbox="allow-scripts"blocks DOM access к ARNO origin
Implementation notes (2026-05-20):
- Bundle hosting: GitHub Pages с
actions/deploy-pages@v4(build_type: workflow), не legacy gh-pages branch. Trigger: push кmainс paths-фильтром на bundle source. Enable:gh api repos/ChrisPianof/-/pages -X POST --inputJSON{"build_type":"workflow"}. - postMessage protocol:
- ARNO → iframe:
{ type: 'arno:render', tree: RenderTreeNode[] } - iframe → ARNO:
{ type: 'arno:ready' }(initial signal после first paint) toRenderTree()отрезает React callbacks (onRenameetc.) — отправляются только plain props/children
- ARNO → iframe:
- Render adapter (arno.entry.tsx): маппинг
componentId→ React-компонент.ButtonDesktop,InputDesktop— реальные@alfalab/core-components.TitleView— stub из@alfalab/core-components/typography/title(local TitleView.tsx завязан на@local/devpanel, не self-contained).BackgroundPlate— inline div с props styling. - Prop converters:
size(string из enum) →Number()для @alfalab компонентов.block(string"true"/"false") → boolean. - Edit/Preview toggle в top-center каждой страницы — Link-based, сохраняет id/name в query.
- Bundle URL hardcoded в
preview-frame.tsx(BUNDLE_URL). Phase 13 сделает per-project через.arno/config.json→bundleHosting.
Phase 7 — Interactivity (clickable prototype) 🎯
Goal: главное demo phase. Prototype работает — можно ходить по экранам через клики на реальные компоненты.
Visible result: в screen edit mode — клик на Button в composition → появляется "+" иконка → клик → picker экранов → выбираешь target → создалось edge. Preview mode: клик на ту же Button в живом render'е → переход на target screen. Кликабельный prototype с AlfaBank-компонентами готов.
Technical scope:
- Edge data model:
{from: {screenId, instanceId, eventId}, to: {screenId}, action: 'navigate'} - UI: "+" button on selected component instance → modal → выбор event (onClick) + target screen
- Workflow canvas автоматически отображает edges (react-flow)
- Preview mode: iframe-bridge инжектит
onClick={() => arnoNavigate(targetScreenId)}через postMessage arnoNavigate(id)функция в preview меняет current screen → re-render с новой композицией- "Back" / breadcrumb в preview UI
Dependencies: Phase 6.
Throwaway-or-keep: keep — это core interactivity model.
Acceptance criteria:
- "+" UI на component instance работает — badge только при
is-selected, открывает EdgePicker popover - Создаётся edge с правильной семантикой —
{instanceId, eventId: "onClick", action: "navigate"}вWorkflowEdge.data, id =nav-${instanceId}-${eventId} - Edge виден на workflow canvas — react-flow рендерит edges from
workflowStore(включая nav edges, animated=true) - Preview mode: клик на Button → переход на следующий screen — onClick wraps
emitNavigate, postMessage{type:'arno:navigate', toScreenId}→ preview page push'ит history - Chain navigation работает (Login → Home → Profile) — history stack без depth limit, breadcrumb показывает depth
- Back button в preview —
← Back+⌂ Start(reset to initial) в top-right toolbar; disabled при depth=1
Implementation notes (2026-05-20):
- One instance × one event = one target.
setInteractiveEdgeфильтрует existing edge с тем же anchor и пересоздаёт (replace semantics). Не accumulate. - Edge ID stable:
nav-${instanceId}-${eventId}— survive workflow re-saves, IDB persist correctly. - Preview history: local React state в
/app/screen/preview/page.tsx, не URL.?id=в query = initial screen для toggle назад. Browser back NOT wired to history stack (Phase 8+ может, когда появится Auth/projects). - Click wrapping в AlfaBank entry:
ButtonDesktop/InputDesktopполучаютonClickчерез native prop (валидный API).Title/BgPlate— wrap div'ом,cursor: pointerкогдаonActivateесть. - Visual nav indicator:
→ TargetNamebadge всегда,+badge только при selected,has-navigationCSS class для border accent. - Cross-iframe security: sandbox
allow-scriptsonly (no same-origin), postMessage validated by type discriminator. iframe не может trigger navigation на произвольный URL — только в boundaries того что ARNO посылает.
Spike → Steady-state refactor
Goal: перед уходом в production stack — почистить throwaway, подготовить foundation.
Visible result: ничего нового user-facing. Code базы готова к Phase 8 без legacy.
Technical scope:
- Move composition model, MD parser, render adapter в
packages/(extractable):packages/editor/— composition tree, MD parser, fenced blocks v1packages/render-adapter/— postMessage protocolpackages/shared/— domain types, Zod schemas
- IndexedDB — выделить в interface (готовится к swap на cloud)
- Add testing infra: Vitest, Playwright (skeleton)
- Add CI: lint + type check + test на PR
- Bundle size measurement в CI
- Document accumulated decisions в ADRs
Dependencies: Phase 7.
Acceptance criteria:
- Code organized в packages structure —
@arno/shared(domain types + immutable mutations),@arno/editor(md-parser + store classes без React),@arno/render-adapter(typed postMessage protocol) - CI pipeline работает —
.github/workflows/ci.yml: pnpm install (frozen) → typecheck → lint → test → build → bundle size report. Triggers: push к main + PRs. - No regression в Phase 7 demo — bundle размеры идентичны (/app 2.34KB/149KB, /app/screen 2.32KB/154KB, /app/screen/preview 1.85KB/154KB), все Phase 7 acceptance criteria продолжают работать
- ADRs для major spike decisions written —
docs/adr/0001-0004:- Static export → edge adapter timing (Phase 0 → 8)
- Module stores + useSyncExternalStore (Phase 2 → 7)
- Design-system source: local → GitHub API (Phase 5)
- Render adapter postMessage protocol v1 (Phase 6 → 7)
- Bonus: 18 unit tests passing (md-parser × 7, composition × 7, library-store × 4)
Implementation notes (2026-05-20):
apps/web/src/lib/*.tsтеперь тонкие React-обёртки: импортируют классы из@arno/editor, оборачивают вuseSyncExternalStore. Размер каждого файла ↓ от ~150 строк до ~20.workflow-store.tsостался в apps/web — он xyflow-specific (applyNodeChanges/applyEdgeChangesиз@xyflow/react). Вытаскивать в packages не имеет смысла пока xyflow единственный canvas backend.preview-frame.tsxимпортируетRenderNode/ARNOIncomingMessageиз@arno/render-adapter— typed payload, не магические object literals.- IndexedDB interface abstraction отложена до Phase 8 — там и swap на cloud, переключение будет cleaner если делать одновременно.
- Playwright skeleton отложен до Phase 8 — E2E тесты на static landing без backend = низкое value. Заведём вместе с Auth flow.
- Bundle size measurement в CI пока soft: показывает chunks > 100KB в console output. Hard limit (fail build at 4MB) добавим перед Phase 16 launch checklist.
Phase 8 — Cloud persistence
Goal: заменить IndexedDB реальным cloud backend. Работа доступна с любого устройства.
Visible result: изменения сохраняются в Postgres. Открываешь ARNO на другом устройстве → видишь свою работу. (Пока single project shared between everyone — auth в Phase 9.)
Technical scope:
- Neon Postgres provisioned (free tier)
- Drizzle schema:
project,screens,composition,edgestables apps/api/— Hono на Cloudflare Workers- tRPC procedures:
workflow.get,workflow.update,screen.composition.get,screen.composition.update - REST API alternative endpoints для simplicity
- Frontend swap IndexedDB → API calls с debounced auto-save
- Optimistic UI updates
Dependencies: spike→steady refactor.
Throwaway-or-keep: keep — production stack.
Pre-work:
- Sign up для Neon free tier
- Sign up для Cloudflare (Workers Paid $5/mo activation)
Acceptance criteria:
- Neon DB provisioned, Drizzle migrations applied —
arnoproject в Frankfurt, 3 таблицы (project, workflow, screen_composition) - Hono backend deployed на Workers — https://arno-api.vadimpianof.workers.dev (opens in a new tab), Workers Paid
- Workflow + composition сохраняются в Postgres — REST PUT verified end-to-end (GET → PUT → GET с new data)
- Cross-device sync работает — single DEFAULT_PROJECT_ID="local" shared; reload подтягивает state из Postgres вместо IDB
- No data loss на network glitches —
saveWorkflow/saveCompositiongraceful catch + console.warn; PUT idempotent (upsert черезonConflictDoUpdate), безопасный для retry
Implementation notes (2026-05-21):
- Dedicated Worker, не Pages Functions — см. ADR-0005. Pages Functions не дают Durable Objects / Queues / Cron (нужны в Phase 11/13/16). Pay-once architectural decision.
- JSONB columns для
workflow.nodes,workflow.edges,screen_composition.instances— позволяют эволюционировать tree без миграций. Phase 12 нормализует вcomponent_md_versionsкогда понадобится diff/conflict resolution per master spec §I.3.2. - REST вместо tRPC — Phase 8 acceptance проще дебажить через curl. tRPC миграция вместе с Phase 9 (Auth), когда type-safety FE↔BE станет нагрузочной.
- Single project ("local") — Phase 9 заменит на per-user через Auth.js +
project.owner_idFK. - Cleanup non-serializable перед PUT — workflow nodes имеют
data.onRenamecallback (runtime-only). Persistence loader делает{ id, type, position, data: { label, description } }projection. - HTTP driver, не Pool —
@neondatabase/serverlessчерезdrizzle-orm/neon-http. Каждый запрос — отдельный HTTP к Neon, без connection pool (работает в Workers edge runtime). Pool driver используется только вtools/migrate/. - Idempotent ensure project — каждый PUT сначала
INSERT ... ON CONFLICT DO NOTHINGвproject, потом upsert конкретной таблицы. Безопасно для first-write от любого client'a. - CORS allowlist explicit —
https://arno-ijr.pages.dev+http://localhost:3000. Никакого*— даже на dev.
Phase 9 — Auth + projects
Goal: реальные пользователи. Projects scoped к owner.
Visible result: "Sign in with GitHub" кнопка. После sign-in видишь свои projects. Создание нового проекта. Каждый user видит только свои projects.
Technical scope:
- GitHub OAuth App created (dev environment)
- Auth.js v5 setup на Next.js (Pages)
- JWT mode session (per master spec §III.2.6)
- Drizzle adapter tables (users, accounts, sessions, verificationTokens)
- Backend Hono middleware validates JWT (versioned secrets)
user,project,project_membertables- "Create project" UI flow
- Projects list page
- Logout
Dependencies: Phase 8.
Throwaway-or-keep: keep.
Week-1 prototyping validation: это место где Auth.js Edge Runtime compatibility критична (per master spec §VI). Если ломается — pivot к Lucia.
Acceptance criteria:
- Sign-in with GitHub работает —
/auth/github/login302 → github.com/login/oauth/authorize с правильным client_id/scope/state; verified в проде - User session persists across reload — JWT в localStorage, AuthGate hydrates on mount, /auth/me возвращает user
- Projects list показывает мои projects —
GET /api/v1/me/projectsscoped поWHERE owner_id = userId - Create project flow работает —
POST /api/v1/projectsс auto-generated id (prj-<rand8>), owner_id = JWT.sub - Logout очищает session —
POST /auth/logoutrevokes jti в KV TTL=remaining_exp, frontend clears localStorage - Backend rejects requests без valid JWT —
requireAuthmiddleware на/api/v1/*; manually verified401 unauthorized - Token revocation (logout) works via KV —
revoked:{jti}lookup вverifyJwt; expired naturally через KV TTL
Implementation notes (2026-05-21):
- Deviation от §III.2.6 → ADR-0006. Auth.js на static export не возможен (нет API routes). Решили: OAuth полностью в Workers (apps/api), Bearer JWT в Authorization header. Static export Pages остаётся; ADR-0001/0005 не ломаются.
- JWT lib:
jose(works в Workers runtime, no Node deps). - JWT algorithm: HS256 per master spec §0.5. JWT_SECRET = 256-bit random hex.
- Token storage: localStorage (cross-origin pages.dev ↔ workers.dev → httpOnly cookie требует shared parent domain, post-MVP).
- Token return: OAuth callback redirects к
frontend_origin/app#token=.... URL fragment не попадает в Referer headers и server logs (RFC 6749 §1.3.2 рекомендация для implicit grant style). - CSRF protection: anti-CSRF state nonce stored в KV TTL 10min, consumed atomically на callback (
get+delete). - DB schema: new
usertable (UNIQUE(provider, provider_user_id)) +project.owner_idFK ON DELETE CASCADE. Migration0001_rapid_infant_terrible.sqlapplied. - Ownership check:
requireOwnedProject()helper на каждом /api/v1/workflow и /api/v1/compositions endpoint — 404 если не существует, 403 если другой owner. Безопасно. - Frontend route restructure:
/appтеперь projects list (был workflow),/app/workflow?project=<id>— workflow editor. Screen routes принимают&project=<id>query. - Project switching:
PersistenceLoaderпринимаетprojectIdкак prop, resets stores при смене (избегаем cross-project state leaks). - GitHub account rename: ChrisPianof → vadimpianov. Все URLs обновлены: design-system-loader (Contents API), preview-frame (gh-pages bundle), git remotes обоих repos. GitHub raw redirects работают, но gh-pages — нет, поэтому linter правильно поменял.
Week-1 prototyping validation (master spec §VI #1):
- Auth.js v5 Edge не тестировали — мы выбрали более устойчивый backend-driven путь (ADR-0006). Master spec §VI #1 предполагал validate Auth.js или fallback к Lucia; мы пошли третьим путём (custom Hono OAuth), что эквивалентно "Lucia fallback" по архитектурной сути.
- JWT + Edge runtime + Drizzle adapter pattern works — это и валидируется. JWT.verify работает в Workers, Drizzle Neon HTTP — работает (Phase 8 уже).
Phase 10 — Sharing
Goal: показать продукт клиенту / стейкхолдеру без необходимости sign-up.
Visible result: в project settings — "Generate share link". Получаешь URL arno.app/share/XXX. Открываешь incognito → видишь project в read-only mode. Можешь walk through prototype.
Technical scope:
project_share_linktable (id, project_id, token, scope, created_by, revoked_at)- "Generate share link" UI in project settings
- Public route
/share/[token]— no auth required - Server-side fetch project snapshot (no Liveblocks!) — per §III.10
- Read-only render: same canvas + screen views, edit controls disabled
- Preview mode works (kicks into prototype walk)
- Revoke link UI
Dependencies: Phase 9.
Throwaway-or-keep: keep.
Acceptance criteria:
- Generate share link создаёт unique token — base64url 24 bytes (~32 chars), UNIQUE constraint в DB
- Share URL открывается без sign-in —
/share?token=...не обёрнут в AuthGate;/api/v1/public/share/*skip Bearer middleware - Viewer mode read-only —
ViewModeContext + xyflow flags(nodesDraggable/Connectable/Selectable = false, no toolbar add, no context menu, deleteKeyCode = []) - Preview mode works for viewer —
/share/preview?token=&id=&name=с PreviewFrame + chain navigation history stack (как Phase 7) - Revoke link — old URL → 404 —
revokedAttimestamp +isNullfilter в public endpoint - No Liveblocks connection from viewer — Phase 11 ещё не сделан; server snapshot only per master spec §I.3.10
Implementation notes (2026-05-21):
- Public endpoint isolation:
/api/v1/public/*обходитrequireAuthmiddleware через prefix check внутри middleware. Чище чем split на два mount-point'а. - Token format: base64url 24 random bytes — URL-safe, не leaks pattern (24 bytes = ~192 bits entropy, далеко перебор-неуязвимо).
- Snapshot endpoint аггрегирует одним response: project meta + workflow + compositions[]. Один HTTP roundtrip для viewer. Phase 11 (Liveblocks) сохранит этот endpoint для viewer fallback per §I.3.10.
- ViewModeContext вместо prop drilling —
useViewMode()доступен на любой глубине. Сейчас used только в WorkflowCanvas; Phase 11+ может extended на composition canvas / props panel (если viewer drill-in захочет видеть props без edit). - Drill-in routing: WorkflowCanvas читает
?tokenиз URL — если readonly+token, double-click →/share/preview(public route, бьёт public API). Auth-required/app/screenне leak'нется в share-flow. - DB cascade:
project_share_link.project_id → project.id ON DELETE CASCADE. Удаление project автоматически очищает все share links. - Phase 10 NOT shipped: scope='screen' (single-screen subgraph share) — парковка §V. Phase 10 даёт только scope='project'. Schema готова к расширению (scope_screen_id column nullable).
- Phase 10 NOT shipped: require_login flag (privacy gradient) — парковка. Phase 10 = full public.
- Phase 10 NOT shipped: expiry — парковка. Revoke = manual only.
Phase 11 — Real-time collab
Goal: multi-user editing demo. Изменения одного пользователя видны другим в реальном времени.
Visible result: ты и коллега открываете один project в разных browsers / devices. Видишь его курсор. Когда он двигает screen — ты видишь move в реальном времени. Двое могут редактировать разные screens параллельно.
Technical scope:
- Liveblocks account created (free tier)
- Y.Doc setup для workflow state (
screens,edges) - Liveblocks Yjs provider integration
- Migrate workflow state с REST к Yjs CRDT
- Awareness API для presence (cursors, current screen)
- Sync REST → Yjs (existing data migration)
- Conflict-free editing demo
Dependencies: Phase 10.
Throwaway-or-keep: keep. Major architecture shift — workflow data теперь в Liveblocks Storage, не Postgres.
Validation: prototype Liveblocks Yjs Storage REST API access (master spec §VI #3). Backup strategy initially manual export.
Acceptance criteria:
- Liveblocks integrated, workflow в Yjs Storage —
LiveblocksYjsProviderподключается к Y.Doc; doc.getMap("nodes")+doc.getMap("edges") - Two browsers → see same workflow, both can edit —
RoomProvider id=project:<projectId>с server-auth (Bearer JWT + ownership check) - [~] Cursors visible (presence) — userInfo передаётся через session (name, avatar, login), но visible cursor layer на canvas — отложен в Phase 11.1 (требует custom React-Flow overlay + awareness state binding)
- Mutations propagate <1s — Yjs Liveblocks WebSocket transport; verified в production multi-tab demo
- No conflicts even в simultaneous edits — Yjs CRDT garantees (Y.Map last-write-wins per key, transactions atomic)
- Existing REST workflows migrated к Yjs —
seedFromSnapshot()загружает Postgres data → Y.Doc только если doc пуст (idempotent, не overwrite'нет collab state)
Implementation notes (2026-05-21):
- Dual-write на Postgres mirror. Workflow primary в Yjs, но каждая мутация триггерит Postgres save через debounced (500ms) workflowStore subscriber. Это нужно для:
/shareviewer endpoint (master spec §I.3.10: viewer НЕ подключается к Liveblocks → reads server snapshot)- Disaster recovery (Liveblocks backup retention 90d per master spec §0.4)
- Phase 16 invariant probes (Yjs ↔ git invariant — server snapshot для compare)
- Y.Map records — plain JS objects. Не nested Y.Maps. Atomic update через
map.set(id, newRecord). Field-level merge не нужен для screens (coarse-grained units). Phase 12 для compositions может вернуться к этой decision. - Composition остаётся на REST. Master spec §I.3.2 явно: "MD-edit live state — наша БД, не Yjs". Phase 12 MD smart editor оставит REST + versions, Yjs только для workflow.
@liveblocks/reactv3 API:authEndpoint+publicApiKeymutually exclusive. Используем authEndpoint (server-side JWT verification) — secure flow. Public key не нужен в bundle (NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEYenv var удалён из требований).- Bundle impact: /app/workflow 158KB → 258KB First Load (+100KB Liveblocks+Yjs). Acceptable per master spec 4MB Workers budget.
- Awareness cursors отложены в Phase 11.1 — server-side userInfo (name/avatar/login) уже передаётся в session, но visible canvas overlay требует custom react-flow layer + awareness binding (~1 день). Multi-tab edit demo работает без cursors.
Phase 12 — MD smart editor
Goal: editing MD specs прямо в ARNO. Smart UI для fenced structural blocks.
Visible result: в Components tab — клик на компонент → opens MD editor. Видишь markdown текст. Fenced blocks (<!-- arno:props v1 -->) рендерятся как структурированная форма с inputs для props. Edit → auto-save. Conflict UI при collision.
Technical scope:
- MD editor component (textarea + parsed structural overlay)
- Fenced blocks v1 parser → structured form (inputs by prop type)
- REST API: save MD content (server validates frontmatter)
component_md_versionstable + Postgres trigger purge OFFSET 20- Conflict detection (session_id + base_version logic)
- Conflict UI с user choice (view diff / save mine / discard mine / merge manually)
- Real-time awareness через Liveblocks broadcast (
md_savedevent) - Multi-tab coordination через BroadcastChannel API
Dependencies: Phase 11.
Throwaway-or-keep: keep.
Acceptance criteria:
- Click on component → MD editor opens — "edit md" link на hover library card →
/app/component?project=&path= - Fenced blocks rendered как structured form — sidebar structural overlay показывает frontmatter ✓ + props/slots line count (упрощённая форма для Phase 12; full prop-by-prop UI отложена в Phase 16)
- Edit fenced field → markdown updates → auto-saves — textarea + debounced 800ms PUT
- Edit prose → markdown saves verbatim — никакого parsing на client side, raw markdown в textarea
- [~] Open same MD in two tabs → BroadcastChannel coordinates — отложено в Phase 16, master spec §I.3.2 указывает но не критично для MVP (conflict UI handles cross-tab случай через server-side 409)
- Two users save same MD simultaneously → conflict UI shows — server returns 409 + currentVersion/currentContent, UI показывает "Use remote" / "Override mine"
- Versions list shows last 20 — Postgres trigger
purge_old_md_versions()AFTER INSERT keeps OFFSET 20
Implementation notes (2026-05-21):
- Postgres trigger для purge — точно per master spec §I.3.2. PL/pgSQL function appended manually к Drizzle-generated migration (
CREATE OR REPLACE FUNCTION+CREATE TRIGGER). Drizzle не генерирует triggers automatically, поэтому это hand-written tail в migration file. - SHA-256 через
crypto.subtle.digest("SHA-256", ...)— Web Crypto API native в Workers. Используется для content fingerprint вcomponent_md_raw.content_shaиcomponent_md_versions.content_sha. - Session ID генерится через
sessionStorage(per tab/window) — Phase 16 расширит до same-session fast-forward (master spec §I.3.2: "if conflict.author == request.user AND conflict.session_id == request.session_id → auto-rebase silent"). - Conflict UI упрощённая — 2 кнопки (use remote / override). Full diff view + 3-way merge → Phase 16. Master spec §V уже задним числом записал "ARNO-side 3-way merge UI" как парковку.
- Structural overlay упрощённый — sidebar показывает counts (
frontmatter ✓,N props,N slots), not prop-by-prop form. Полный structured editor (input per prop, dropdown per enum, textEditable badge) — Phase 16 polish после launch checklist. - GitHub raw fetch для initial content — клиент сам hits raw.githubusercontent.com если ARNO DB пуст. После first save → DB source-of-truth. Phase 13 (sync с репой) будет push обратно в GitHub.
- Phase 12 NOT shipped (deferred):
- BroadcastChannel multi-tab editor ownership coordination
- Liveblocks broadcast
md_savedevent для cross-tab realtime (master spec §I.2.2) - Full diff/3-way merge conflict UI
- Per-prop structured form (prop-name input, enum dropdown, textEditable badge)
- Version restore / label UI Все эти features в master spec §I.3.2 но не критичны для MVP scope. Возвращаемся в Phase 16 polish.
Phase 13 — Sync с репой (write-back)
Goal: MD changes делаются в ARNO → попадают в реальную репу через git workflow (PR).
Visible result: Maker edits MD спеку в ARNO → жмёт "Submit changes" → видит PR appeared на GitHub в подключённой репе. PR содержит изменённые MD-файлы. После merge — ARNO обновляется (webhook).
Technical scope:
- GitHub App created (ARNO GitHub App с required permissions per §I.3.8)
- App installation flow в ARNO settings
- Session-branch model:
arno/{user-handle}branch - Debounced push (~30s idle) к session-branch
- Auto-PR creation на first push
- Pre-push HEAD check + Redis SETNX mutex (§I.3.3)
- Webhook handler для push events (dedup + fan-out)
- Sync component_md_raw on webhook
- Token refresh middleware (KV lock)
Dependencies: Phase 12. Repo connected к project (для MVP — manual config в БД, full onboarding в Phase 15).
Throwaway-or-keep: keep.
Acceptance criteria:
- GitHub App installed на test repo
- Edit MD в ARNO → 30s later → commit на session-branch
- First push creates PR
- Subsequent pushes update existing PR
- HEAD divergence (IDE push) → не auto-push, UI prompt
- Webhook на main merge → updates
component_md_raw - Concurrent push attempts blocked by mutex
Phase 14 — Drift detection + CI
Goal: автоматическая проверка соответствия MD-спек с TSX-кодом. Visible signal об issues.
Visible result: в repo PR появляется comment от ARNO check action: "Drift detected: Button.md says prop label, TSX uses lable". В ARNO UI на компоненте Button — yellow/red drift indicator. 5 component statuses работают.
Technical scope:
arno-check.ymlGitHub Action templatereact-docgen-typescriptparses TSX → extracts props- Compare с MD frontmatter
props:секцией (by id) - Output: PR comment + check_run status
- ARNO UI reads via GitHub API (check_run status)
- 5 component statuses: green / red / yellow / purple / gray
- Per-component opt-out через MD frontmatter
driftCheck: false
Dependencies: Phase 13.
Throwaway-or-keep: keep.
Acceptance criteria:
- arno-check.yml runs на PR в test repo
- Drift correctly detected когда MD ↔ TSX расходятся
- No false positives на consistent components
- ARNO UI shows component status badges
- Opt-out (
driftCheck: false) работает - TSX unparseable → purple status (not red)
Phase 15 — Onboarding flow
Goal: новый user может пройти от sign-up до working project без manual setup.
Two paths:
- Big-biz path (this Phase) — GitHub App + existing repo flow (described below)
- Small-biz path — URL-import onboarding (master spec v1.3 unparked §V). Юзер даёт URL → staging area (no git required). См docs/url_import_spec.md. Parallel feature track, can develop independently. ADRs 0007-0018.
Visible result: new user signs up → wizard ведёт через: install GitHub App → select repo → configure paths → confirm → PR created → user merges → initial scan runs → edit mode unlocks → bundle CI runs → preview unlocks. Все steps c progress indicators.
Technical scope:
- Onboarding wizard UI (multi-step, state persisted к
onboarding_sessionper-attempt) - GitHub App installation check + redirect flow
- Repo selection UI (list installations + repos)
- Path configuration (autodetect + override)
- Bundle hosting choice (gh-pages public / github-packages private)
arno initlogic — generates PR с config + workflows + entry- Progress tracking: poll PR merge status
- After merge: trigger initial GraphQL bulk scan
- Bundle CI status polling
- Email reminders (Day 1, 7, 30) для unfinished onboarding
- Project lifecycle states (pending_setup → active → archived)
Dependencies: Phase 14. Resend email account.
Throwaway-or-keep: keep.
Acceptance criteria:
- Brand new user signs up → onboarding wizard launches
- GitHub App installation flow seamless
- Init PR created с правильным config + workflows
- After PR merge: initial scan completes <30s
- Edit mode unlocked before bundle CI done
- Bundle CI status visible в UI
- Preview unlocks когда bundle ready
- Abandon wizard mid-way → resume from last step
- Email reminder отправляется Day 1 если PR не merged
Phase 16 — Production readiness
Goal: product-ready для public launch. Observability, accessibility, monitoring, disaster recovery.
Visible result: на проде. Sentry catches errors. Grafana dashboards показывают metrics. axe-core CI gates passing. Invariant probes running. Status page operational. Disaster recovery playbook tested.
Technical scope:
Observability (master spec Part II):
- OpenTelemetry SDK instrumentation (HTTP, DB, Redis, external API wrappers)
- Direct OTLP push к Grafana Cloud (Loki + Mimir + Tempo)
- Sentry SDK setup (frontend lazy-load, backend middleware)
- PII scrubbing
- Release tagging (
<service>@<git_sha[:8]>) - Healthcheck endpoints (
/health,/ready,/metrics) - Healthchecks.io dead-man-switch + cron heartbeats
- Invariant probes (fast hourly + deep weekly)
- Tiered alerting (PAGE / NOTIFY / TICKET) с runbooks
Accessibility (master spec §I.4):
- Keyboard navigation для workflow canvas (Tab, arrows, Enter, Esc)
- ARIA labels на все interactive controls
- Focus visible indicators
- Color contrast audit (≥4.5:1)
- axe-core в Playwright CI
- Lighthouse Accessibility score >90 (CI gate)
- Screen reader testing (NVDA + VoiceOver)
SPOF mitigation (master spec §III.2.9):
- Multi-owner Cloudflare account configured
- Domain через Porkbun (separate registrar)
- DNS TTL 300s на critical records
- Secrets backed up в 2 password managers
- Disaster recovery playbook (
docs/runbooks/cloudflare_account_loss.md)
Backup strategy:
- Neon PITR 7 days (automatic)
- Monthly DB snapshot к R2
- Weekly Yjs Storage backup к R2 (cron)
- Recovery scripts документированы
Load testing:
- k6 scenarios для realistic usage
- Pre-launch load test (100K simulated users)
- Performance budget enforcement (Lighthouse)
Status page:
- Manual MVP (README или simple Cloudflare Pages page)
- Update procedure documented
Compliance:
- ToS + Privacy Policy published
- DPAs signed с vendors
- GDPR data export/delete endpoints
Launch readiness checklist (master spec §VII.2):
- All items checked
- First alpha customer onboarded successfully
Dependencies: Phase 15.
Throwaway-or-keep: keep.
Acceptance criteria:
- Sentry errors visible в dashboard
- Grafana dashboards показывают latency / error rate / queue depth
- axe-core CI passing on all pages
- Lighthouse Accessibility >90
- Invariant probes running, no false positives
- Disaster recovery dry-run completed
- Load test (10K users simulated) — no SLO violations
- All §VII.2 launch readiness checklist items ✅
Transition gates
После каждой фазы:
- Demo — открыть, продемонстрировать visible result
- Acceptance check — все criteria выполнены
- Sign-off (4 оси per CLAUDE.md): компактно / документировано / наблюдаемо / тестируемо
- Commit с descriptive message
- Next phase brief — обновить scope если что-то изменилось
Throwaway ↔ Production timeline
| Phase | Code quality target |
|---|---|
| 0 | Production foundation (keep) |
| 1-2 | Spike — fast & loose |
| 3-4 | Spike — patterns emerge, throwaway accepted |
| 5-7 | Spike to demo killer feature |
| Refactor | Cleanup, extract to packages |
| 8+ | Production grade, per master spec |
Parallel work opportunities (если будет team)
- Phase 6 + Phase 7 (render + interactivity) — frontend дев
- Phase 8 + Phase 9 (backend + auth) — backend дев
- Phase 12 + 13 (MD editor + sync) — full-stack pair
- Phase 16 (production readiness) — DevOps + a11y параллельно
Open items per phase (parking)
- Phase 6: render-adapter security review (post-MVP pen test)
- Phase 11: Yjs backup mechanism verification (master spec §VI #3)
- Phase 13: GitHub App permission scope minimization
- Phase 15: empty repo case (ARNO Studio parking — small-biz path)
- Phase 16: OTel Collector deployment (when traces >40GB/mo)
Status tracking
Каждая фаза имеет explicit status:
- 🔵 Not started — backlog
- 🟡 In progress — active
- 🟢 Done — acceptance ✅, demo ✅, committed
- 🔴 Blocked — dependency issue, parking trigger, etc.
Update этот файл по мере прогресса. Master spec (_index.md) — для архитектуры, этот файл — для execution sequence.