Implementation workflow

Цель: последовательность фаз разработки ARNO, где после каждой итерации есть visible artifact — можно посмотреть, потрогать, продемонстрировать.

Принципы

  1. Vertical slicing > horizontal layers. Каждая фаза = full-stack vertical через текущую feature.
  2. Demo-driven. Каждая фаза заканчивается чем-то что можно открыть в браузере и показать.
  3. Throwaway acceptable в spike phases (1-7). Mocks → real services постепенно.
  4. Cumulative. Каждая фаза строит на предыдущей.
  5. AlfaBank как первый customer / testbed. Реальные MD + TSX уже существуют в Desktop/Claude/AlfaBank/. Dogfood с самого начала.
  6. Master spec (_index.md) — canonical reference для tech decisions. Workflow здесь — последовательность, не дублирует архитектурные решения.

Phase overview

#PhaseVisible resultDaysStatus
0Foundation skeletonBlank Next.js на *.pages.dev1🟢 done — https://arno-ijr.pages.dev (opens in a new tab)
1Static workflow canvasMock граф из 5 экранов с рёбрами2🟢 done — https://arno-ijr.pages.dev/app (opens in a new tab)
2Editable canvas (local)Создание/перемещение экранов и рёбер2🟢 done — https://arno-ijr.pages.dev/app (opens in a new tab)
3Screen editor (local)Drop компонентов из mock-library, configure props3🟢 done — https://arno-ijr.pages.dev/app/screen (opens in a new tab)
4Local persistenceIndexedDB — работа переживает reload1🟢 done — auto-saves to arno-local DB
5Real DS from AlfaBankLibrary показывает реальные компоненты из MD3🟢 done — GitHub Contents API → raw MD → parse (repo ChrisPianof/-)
6Preview mode = live renderiframe с настоящим React-рендером AlfaBank Button4🟢 done — bundle on chrispianof.github.io + sandboxed iframe
7Interactivity (clickable prototype) 🎯Кликабельный prototype с реальными компонентами2🟢 done — + picker + navigations + history stack
Spike→steady refactorCleanup throwaway, prep prod-stack2🟢 done — 3 packages, 18 unit tests, CI, 4 ADRs
8Cloud persistenceNeon + Hono + Drizzle, work cloud-backed3🟢 done — https://arno-api.vadimpianof.workers.dev (opens in a new tab)
9Auth + projectsGitHub OAuth, projects scoped к user2🟢 done — backend OAuth + per-user projects (ADR-0006)
10SharingShare-link, anonymous viewer2🟢 done — /share + /share/preview public read-only
11Real-time collabLiveblocks + Yjs, multi-user live3🟢 done — workflow в Yjs Storage, multi-tab sync
12MD smart editorFenced blocks, versions, conflict UI5🟢 done — /app/component editor + versions trigger-purged
13Sync с репойSession branches, auto-PR, GitHub App4
14Drift detection + CIarno-check.yml, 5 statuses в UI3
15Onboarding flowFull first-run UX3
16Production readinessObservability, a11y, probes, launch checklist5

Итого ≈ 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 via pnpm build static export — out/index.html содержит "ARNO", "Cloud design editor", "Coming soon"; pnpm lint / pnpm typecheck clean)
  • 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).
  • onRename callback инжектится через nodesWithHandlers memo (xyflow требует immutable data refs).
  • onKeyDownCapture stopPropagation на input rename — критично, иначе Backspace во время редактирования удаляет ноду через ReactFlow's deleteKeyCode.
  • ReactFlowProvider wrap — для возможности использовать 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 + Suspense boundary, обязательно для Next.js static export). Эквивалент [id] dynamic param без backend.
  • State: composition-store.ts — module-level singleton + React 18 useSyncExternalStore. Persists между /app/app/screen navigation (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 500mssaveWorkflow / saveComposition
  • Reload → state восстанавливается — PersistenceLoader на mount грузит из IDB, hydrates stores до first render
  • Closing/opening browser preserves work — IndexedDB arno-local persists 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-local v1, 2 object stores — workflow (keyPath id, единственная запись id="default"), compositions (keyPath screenId).
  • 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.
  • onRename callback (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.ts custom 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 library store, 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 snapshot public/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:

  1. Создать 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)
      }
    })
  2. GitHub Action .github/workflows/arno-build.yml — собирает bundle, publishes к gh-pages
  3. Enable GitHub Pages на AlfaBank repo (settings)
  4. 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"> с inline srcdoc (нет 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 рекурсивный, slot children
  • 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 --input JSON {"build_type":"workflow"}.
  • postMessage protocol:
    • ARNO → iframe: { type: 'arno:render', tree: RenderTreeNode[] }
    • iframe → ARNO: { type: 'arno:ready' } (initial signal после first paint)
    • toRenderTree() отрезает React callbacks (onRename etc.) — отправляются только plain props/children
  • 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.jsonbundleHosting.

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: → TargetName badge всегда, + badge только при selected, has-navigation CSS class для border accent.
  • Cross-iframe security: sandbox allow-scripts only (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 v1
    • packages/render-adapter/ — postMessage protocol
    • packages/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:
    1. Static export → edge adapter timing (Phase 0 → 8)
    2. Module stores + useSyncExternalStore (Phase 2 → 7)
    3. Design-system source: local → GitHub API (Phase 5)
    4. 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, edges tables
  • 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 — arno project в 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 / saveComposition graceful 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_id FK.
  • Cleanup non-serializable перед PUT — workflow nodes имеют data.onRename callback (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 explicithttps://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_member tables
  • "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/login 302 → 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/projects scoped по WHERE owner_id = userId
  • Create project flow работает — POST /api/v1/projects с auto-generated id (prj-<rand8>), owner_id = JWT.sub
  • Logout очищает session — POST /auth/logout revokes jti в KV TTL=remaining_exp, frontend clears localStorage
  • Backend rejects requests без valid JWT — requireAuth middleware на /api/v1/*; manually verified 401 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 user table (UNIQUE(provider, provider_user_id)) + project.owner_id FK ON DELETE CASCADE. Migration 0001_rapid_infant_terrible.sql applied.
  • 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_link table (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 — revokedAt timestamp + isNull filter в 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/* обходит requireAuth middleware через 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. Это нужно для:
    • /share viewer 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/react v3 API: authEndpoint + publicApiKey mutually exclusive. Используем authEndpoint (server-side JWT verification) — secure flow. Public key не нужен в bundle (NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY env 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_versions table + 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_saved event)
  • 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_saved event для 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.yml GitHub Action template
  • react-docgen-typescript parses 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 pathURL-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_session per-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 init logic — 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

После каждой фазы:

  1. Demo — открыть, продемонстрировать visible result
  2. Acceptance check — все criteria выполнены
  3. Sign-off (4 оси per CLAUDE.md): компактно / документировано / наблюдаемо / тестируемо
  4. Commit с descriptive message
  5. Next phase brief — обновить scope если что-то изменилось

Throwaway ↔ Production timeline

PhaseCode quality target
0Production foundation (keep)
1-2Spike — fast & loose
3-4Spike — patterns emerge, throwaway accepted
5-7Spike to demo killer feature
RefactorCleanup, 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.