Операційна модель контексту та довгі завдання

Сьогодні два великі кроки.

У першій частині розберемося, що Claude взагалі знає в момент відповіді: не "пам'ять про проєкт", а конкретне робоче вікно, яким можна керувати.

У другій - як вести роботу, що довша за один підхід: рефакторинг, нову feature або маленький проєкт з нуля, з контрольними точками, відкатом через Git і паралельними сесіями.

Сьогодні на вебінарі пройдемо 2 рівні курсу:
Рівень 5. Операційна модель контексту
Рівень 6. Довгі завдання

Міф "Claude все пам'ятає"

Найчастіша помилка - говорити з Claude як із колегою, який учора дивився потрібний файл і все запам'ятав.

Claude бачить не "пам'ять про проєкт", а поточне вікно контексту: прочитали файл - він у вікні; нічого не завантажили - для моделі це порожнеча. Вікно складається з кількох шарів.

У вікна фіксований розмір, і він залежить від моделі. Точну цифру вашої сесії показує /context.

Але важливіше не число, а сам факт стелі: усі шари ділять вікно між собою, і місце, зайняте одним джерелом, уже не дістанеться іншому. Тому далі говоритимемо про контекст як про бюджет.

CLAUDE.md повертається сам і створює ілюзію, ніби Claude "пам'ятає проєкт". Насправді він каже, як поводитися в проєкті, але не зберігає, яку гіпотезу ви відхилили дві години тому. Тому нова сесія - не амнезія: правила повернуться, а деталей учорашнього розслідування немає, і повернути їх - роль task spec.

flowchart LR A[Ваш поточний запит] --> E[Вікно контексту] B[Прочитані файли] --> E C[Вивід команд і логи] --> E D[CLAUDE.md, rules, memory] --> E S["Службовий шар: tools, skills, MCP"] --> E E --> F[Наступна відповідь Claude]

На схемі видно головне: сигнал потрапляє у вікно тільки через конкретне джерело, а не телепатією. Якщо ви нічого не поклали на стіл, для моделі цього просто немає.


Відбір контексту

"Подивись увесь проєкт і знайди проблему" - спокусливо, але зазвичай шкодить: зайвий контекст конкурує за увагу моделі, міркування пливе.

Контекст - це бюджет якості, і витрачати його варто точно.

Що зазвичай реально допомагає завданню:

А от увесь repository, логи за день і старі неперевірені гіпотези тільки засмітять вікно. Перед тим як додати файл, запитайте себе: "приберу це джерело - Claude працюватиме гірше чи просто зітхне з полегшенням?"

flowchart LR subgraph keep["У вікно"] K1[Task spec] K2[1-3 точні файли] K3[Короткий лог і тест] end subgraph drop["Повз вікно"] D1[Увесь repository] D2[Логи за день] D3[Секрети і .env] end keep --> CC[Чиста сесія] drop -. шум .-> CC

Верхня колонка дає моделі робочу опору, права - тільки конкурує за увагу. Хороший контекст не максимальний, а достатній.


Контекст, коли файлів ще немає

Операційна модель контексту не змінюється, навіть коли проєкту ще немає. Уявіть, що ви створюєте невеликий довідник локалей (коди країн і мов з деталями) з нуля: проєкт порожній, читати поки нічого. Контекст тоді збирається не з коду, а з вашого SPEC.md.

Приклад інтерфейсу довідника локалей Темна таблиця з пошуком і даними про код локалі, мову, країну, валюту і домен. List of locale codes Search by code, language, country or currency FLAG LOCALE CODE LANGUAGE COUNTRY CURRENCY TLD en-US English United States USD .us de-DE German Germany EUR .de ja-JP Japanese Japan JPY .jp
Контекст - це те, що ви поклали на стіл, а не те, що "є в проєкті".

Правило достатнього мінімуму працює і тут: дайте spec і зразок стилю, а не "придумай усе сам".

Якщо поки нема з чого написати SPEC.md, почніть із міні-спеки прямо в чаті: мета, 2-3 обмеження і що перевірити першим. Перший підхід може бути discovery-планом, а не кодом.

/context і /compact

Команда /context показує, чим зайняте вікно.

Читайте її як відповідь на три питання:

Команда /compact замінює детальну історію на summary, частина деталей губиться.

Тому стиснення корисно спрямовувати й прямо сказати, що зберегти.

/compact Збережи:
- мету й критерії приймання з task spec
- область змін: тільки src/auth/*
- відхилену гіпотезу: проблема не в SessionService
- наступний крок: перевірити рендер помилки в LoginController

І пам'ятайте: після стиснення не можна сперечатися "ти ж 20 хвилин тому казав" - у вікні тепер summary, факт піднімайте заново з файлу.

Стиснення трапляється і без вашої команди: коли вікно майже заповнене, Claude Code запускає auto-compact сам - наближення видно заздалегідь за індикатором виду "Context left until auto-compact" у рядку стану. Auto-compact - той самий /compact, тільки список "що зберегти" за вас ніхто не складе. Тому довгу сесію вигідніше стиснути самому на зручній межі, а не чекати, поки це станеться посеред кроку.

І про шари: після стиснення project-root CLAUDE.md, загальні rules і auto memory самі підвантажуються у вікно заново. А от rules, прив'язані до шляхів (path-scoped), і вкладені CLAUDE.md повернуться тільки коли Claude знову прочитає файл із потрібної області - як і вміст самих файлів: від прочитаного залишається рядок у summary, і перед наступною правкою його потрібно відкрити заново.


Що з'їдає вікно до першого питання

Вікно зайняте ще до того, як ви надрукували перше слово, - виглядає це приблизно так:

/context

  System prompt     3.2k   ( 2%)   службові інструкції
  System tools     12.1k   ( 6%)   описи вбудованих інструментів
  MCP tools        21.4k   (11%)   підключені зовнішні сервери
  Memory files      1.8k   ( 1%)   CLAUDE.md, rules, auto memory
  Messages         45.2k   (22%)   ваш діалог, файли, логи
  Free space      116.3k   (58%)

Точний вигляд виводу залежить від версії, а розмір вікна - від моделі. Але картина завжди одна, і з неї випливають практичні висновки:

Просто зараз, якщо Claude Code під рукою: запустіть /context, попросіть прочитати пару файлів і запустіть ще раз - рядок messages виросте на очах.

Забруднення контексту та відновлення

Коли сесія поводиться дивно, справа зазвичай не в тому, що "Claude сьогодні гальмує" (і так буває), а в накопичених у вікні сигналах, які заважають завданню. Це context pollution. Ознаки помітні раніше, ніж здається:

Спочатку відділіть нестачу від забруднення: якщо Claude просить файл і ставить питання - додайте джерело. Якщо впевнено міркує на старому смітті - очищайте. І окрема дія, про яку забувають: якщо файли вже змінені, нічого не робіть, поки не прочитали diff.

flowchart TD A[Сесія поводиться дивно] --> B{Файли вже змінені?} B -->|так| C[Спочатку прочитати diff] B -->|ні| D{Завдання те саме?} D -->|так, історія довга| E["/compact"] D -->|ні, інша тема| F["/clear"] D -->|останній крок повів не туди| G["/rewind"] D -->|хаос і суперечності| H[Fresh session]

Чарівної однієї кнопки немає, і кожен інструмент вирішує свій тип проблеми.

Якщо ви вже використали близько 60% вікна контексту, будьте уважні: агент може почати гірше дотримуватися попередніх інструкцій, повертатися до відхилених рішень або відхилятися від завдання. Помітили такий сигнал — одразу перевірте /context і подумайте, чи не час виконати /compact або вже завершувати сесію.

Сесія як контейнер завдання

"Одна розмова на всі випадки життя" псує і контекст, і diff.

Головне питання в процесі роботи - це все ще одне завдання?
Те саме - продовжуємо;
те саме, але історії багато - стиснути;
змінилося - відкрити новий контейнер.

Команди керування:

flowchart TD A[Нова сесія] --> B[Зафіксувати goal і scope] B --> C[Робота над завданням] C --> D{Це все ще один контейнер?} D -->|Так| E[Продовжувати] D -->|Так, але історії багато| F[Стиснути й повторити опори] D -->|Ні| G[Відкрити новий контейнер] E --> H[Закрити на зрозумілій точці] F --> H G --> H

Сесія - витратний контейнер, а не сімейна реліквія: вона живе рівно стільки, скільки допомагає одному завданню.


Довге завдання ламається в процесі

Довге завдання ламається не в коді, а коли процес втрачає форму: контекст розростається, гіпотези перемішуються, diff стає неможливо читати.

Запит "повністю відрефактори й зроби нормально" перетворюється на широку допомогу там, де ви її не просили. Щоб цього не сталося, завданню потрібен каркас, який відповідає на три питання:

Тому task spec довгого завдання одразу ділиться на етапи - milestones. Усередині такого коридору Claude працює по суті, а не блукає "заради краси", і завжди видно, де завдання перебуває.

Якщо завдання ще розмите, першим milestone може бути розвідка: прочитати 2-3 ключові файли, назвати варіанти рішення й ризики, а вже потім оновити task spec.
flowchart TD A[TASK_SPEC] --> B[Named session] B --> C[Milestone 1] C --> D[Summary + checks + small commit] D --> E[Milestone 2] E --> F[Summary + checks + small commit] F --> G["Stop point / finish"]

Кожен milestone закінчується коротким summary, перевірками й маленьким commit. Саме це робить довге завдання переносимим і перевірним.


Новий проєкт як довге завдання

Збірка проєкту з нуля - це теж довге завдання. Той самий довідник локалей і той самий каркас із milestones працює, тільки етапи тепер про побудову, а не про правку. Правило нарізки одне: кожен milestone закінчується робочим шматком, який можна перевірити, а не "половиною всього одразу":

Це нарізка для випадку, коли форма проєкту вже зрозуміла. Якщо продукт ще неясний, додайте M0: пісочниця або прототип на один вечір, щоб перевірити гіпотезу і тільки потім зафіксувати нормальні milestones.

Порядок не випадковий: кожен етап спирається на перевірений попередній. Тому якщо на M3 щось зламалося, ви шукаєте проблему в пошуку й фільтрі, а не "десь у проєкті". А маленький commit на кожній межі скоро знадобиться: саме він дозволить відкотити невдалий M3, не чіпаючи корисні M1 і M2.

flowchart TB M1["M1: backend API"] --> C1{Тести зелені?} C1 -->|ні| M1 C1 -->|так: commit| M2["M2: UI зі списком"] M2 --> C2{Список видно в браузері?} C2 -->|ні| M2 C2 -->|так: commit| M3["M3: пошук і фільтр"] M3 --> C3{Фільтр працює вручну й тестом?} C3 -->|ні| M3 C3 -->|так: commit| DONE[Робочий довідник локалей]

Діаграма показує суть дисципліни: вперед можна тільки через перевірку й commit, а невдача повертає всередину поточного етапу, не руйнуючи попередні.

Картина проста: дані - API - інтерфейс. На цьому слайді важливий не стек технологій, а порядок: кожен milestone додає перевірний шар і не ламає вже робочий контракт.

Ім'я сесії, summary, stop points

Безіменна сесія після десяти хвилин починає працювати проти вас: з'являється "та довга про refunds" і "ще одна, але з тестами". Ім'я завдання, гілки й сесії в ідеалі збігаються. А зовнішня пам'ять завдання тримається на кількох простих речах:

Хороший stop point - це місце, де завдання можна безпечно передати навіть майбутньому собі: зрозуміло, що зроблено, що перевірено і який наступний крок.


Context rot і два режими

Context rot - це забруднення на довгій дистанції: контекст старіє, і Claude тягне вже відхилені ідеї як актуальні. Лікування знайоме - зібрати summary і продовжити з чистого стану. Далі постає питання, як саме вести завдання, і тут є два режими:

Точна форма goal-driven залежить від версії: десь є команда /goal <умова>/goal clear знімає ціль), десь ту саму роботу робить явний prompt "працюй, поки тести в test/auth не стануть зеленими" - звіртеся з /help. Сам прийом від версії не залежить.

Goal-driven безпечний тільки за надійного детермінованого фінішу. Умову пишуть так, щоб результат було видно у виводі Claude: "тести в test/auth зелені" працює, бо Claude проганяє тести й результат потрапляє в розмову. На новій feature довідника фініш звучить так само просто: "тест на фільтр локалей зелений".

flowchart LR A[Довге завдання] --> B{Фініш перевіряється машиною?} B -->|так, tests і build green| C["Goal-driven: /goal до умови"] B -->|ні, потрібні рішення людини| D[Покроково: крок - перевірка - рішення] B -->|гроші, міграції, контракти| D

Розвилка проста: машинно перевірний фініш відкриває goal-driven, усе інше безпечніше вести покроково.

Головна навичка тут - не команда, а саме формулювання фінішу, який машина може перевірити.

Checkpoint і /rewind

Checkpoint живе всередині сесії Claude, а Git-коміт - в історії проєкту.

За відчуттям схоже, за наслідками ні: /rewind - це Undo в редакторі, а commit - збережена версія документа в repository. /rewind повертає до здорової розвилки всередині сесії: гілку розмови й часто пов'язані з нею правки файлів.

/rewind - страховка, а не основний workflow. База довгого завдання - git diff, маленькі commits і зрозумілий scope; /rewind використовуємо, поки помилка ще живе всередині сесії Claude.

Але за межами його влади вже зроблений commit і тим більше зовнішні ефекти: база даних, deploy, відправлений запит. І ще один чесний сигнал: третій /rewind до однієї й тієї самої розвилки - не стратегія, а діагноз. Отже, виправляйте task spec і scope або відкривайте fresh session.

Checkpoint створюється тільки перед правками через інструменти Claude. Файли, які змінює shell-команда (rm, mv, cp, скрипти), він не відстежує, і /rewind їх не поверне - для цього залишаються Git і бекапи.
flowchart TD A[Де живе зміна?] --> B[Всередині сесії: гілка й edits] A --> C[В історії проєкту: є commit] A --> D[У зовнішній системі: БД, deploy] B --> B2["/rewind"] C --> C2[Git-based rollback] D --> D2[Rollback цієї системи]

Механіка коротко: checkpoints створюються автоматично перед правками через інструменти Claude. В історію можна зайти через Esc+Esc або /rewind; у меню вибираєте, що повернути - розмову, код, обидва стани або зробити summary from here.

Що раніше ви помітите погану гілку, то вищий шанс, що вона ще живе тільки всередині сесії, де її й поверне /rewind.


Branch, session, worktree

Гілка, сесія і worktree - це три різні шари однієї роботи, і їх легко переплутати:

flowchart LR B["branch: історія змін і commits"] W["worktree: окрема тека з файлами"] S["session: контекст розмови Claude"] B -->|"checkout / worktree add"| W W -->|"відкриваємо Claude тут"| S S -->|"редагує файли"| W W -->|"commit"| B

На схемі видно зв'язку: гілка дає worktree, у worktree відкривається сесія, а правки повертаються комітом у гілку. Шари пов'язані, але очищуються й ламаються по-різному - тому їх і дорого плутати.

Worktree - це друга тека того самого repository без повного клонування. Він доречний, коли в одному TASK_SPEC є незалежні шматки роботи: backend, UI, тести, міграції. Тоді основний agent, окремі сесії або subagents можуть працювати паралельно в різних worktree і не перетирати одні файли.

Зв'язок branch, worktree і session у робочому процесі

Handoff і fresh reviewer

Фраза "начебто готово" не відповідає на жодне інженерне питання. Handoff працює інакше: HANDOFF_NOTE.md - це передбачуваний пакет, де потрібні речі лежать на очікуваних місцях:

Для маленької правки це може бути не окремий файл, а 5 пунктів у PR або issue. HANDOFF_NOTE.md потрібен, коли завдання переживає сесію, день роботи або передачу іншій людині.

Формулюйте сильно, а не розмито: не "тести зелені", а "запущено test, 8 passed"; next step - один конкретний крок, а не "продовжити роботу". Свіжий reviewer - нова сесія Claude або людина - перевіряє за цим пакетом, не як автор, і часто ловить пропущені edge cases, вихід за scope і недоведені твердження. Тільки не давайте йому все листування: інакше він заразиться старими гіпотезами, а потрібен свіжий погляд.

flowchart TD A[Writer session] --> B[HANDOFF_NOTE.md] A --> C[git diff] A --> D[Лог тестів] B --> E[Fresh session] B --> F[Людина-рев'юер] C --> E C --> F D --> E D --> F

Handoff робить завдання незалежним від одного носія знання: його можна продовжити, перевірити або передати без археології по старому чату.


HANDOFF_NOTE.md на одну сторінку

Щоб handoff не залишився теорією, ось повністю заповнена записка для нашого довідника локалей - одразу після M2, коли список локалей уже видно в браузері:

# HANDOFF_NOTE.md

## Goal
Довідник локалей; завершено M2 - UI зі списком локалей з API.

## Changed files
- src/api/locales.js - endpoint GET /locales
- src/ui/locale-list.js - сторінка зі списком локалей
- src/data/locales.json - набір локалей (код, країна, мова, валюта)
- test/api/locales.test.js - тести API

## Decisions
- дані беремо з готового locales.json, а не з LLM - це довідкові факти
- стилі без UI-бібліотеки, звичайний CSS

## Tests / checks
- npm test: 8 passed, 0 failed
- вручну: список локалей видно на localhost:3000

## Known risks
- порожній результат пошуку не оброблено - сторінка виглядає "зламаною"

## Next step
M3: пошук і фільтр по списку + тест на фільтр

Зверніть увагу на формулювання: не "тести зелені", а "8 passed, 0 failed"; не "продовжити роботу", а один конкретний крок M3. Таку записку свіжа сесія Claude або колега читають за хвилину - і одразу працюють, а не відновлюють історію розкопками в чаті.

Підсумок двох частин: керуйте вікном контексту, а довге завдання ведіть як процес - ім'я, milestones, checkpoints, дешевий Git-rollback і чесний handoff. Тоді Claude підсилює розробника, а не тягне в хаос.

Практика - етап 1

Зберемо все на маленькому довіднику локалей. Перший крок - не код, а контекст, і prompt ви пишете самі: завдання в тому, щоб змусити Claude спочатку думати, а не чіпати файли.

Щоб завдання було конкретним, домовимося, що таке одна локаль - на прикладі Австралії:

Цього мінімуму достатньо, щоб і список, і пошук спиралися на одні й ті самі поля.

Спочатку придумайте prompt самі

Мета: "хочу маленький довідник локалей, проєкту ще немає, потрібен план". Перш ніж друкувати, прокрутіть у голові:

Наш варіант


Я хочу зробити маленький довідник локалей для навчального проєкту:
список локалей, у кожної - код (en-AU), мова, країна, валюта, TLD і прапор.
Проєкту поки немає. Спочатку склади план.
Стек простий: Node на backend, чистий HTML/CSS/JS на frontend, без frameworks і npm-залежностей.

Зроби:
1. Сформулюй короткий TASK_SPEC прямо у відповіді, файл поки не створюй.
2. Скажи, який контекст тобі потрібен від мене перед кодом.
3. Розділи роботу на 3 milestones.
4. Для кожного milestone напиши перевірний фініш.
5. Наприкінці переліч файли, які створив би, але поки нічого не створюй.
Це не еталонне рішення, а один із робочих варіантів. Якщо ваш prompt дає Claude ті самі опори - план без коду, spec, milestones з перевіркою - він не гірший за наш. Якщо у вас поки тільки ідея, попросіть Claude спочатку поставити уточнювальні питання й запропонувати 2-3 варіанти scope: не треба вичавлювати повноцінний task spec із туману.

Практика - етап 2

Тепер даємо Claude побудувати один milestone, і prompt знову ваш. Мета не весь проєкт, а M1 з перевіркою й маленьким commit.

Спочатку придумайте prompt самі

Рамка: тільки M1 - мінімальний backend API, який віддає список локалей із готового набору даних, і один тест на endpoint, без UI та пошуку. Подумайте:

Наш варіант

Тепер можна створити проєкт. Спочатку ініціалізуй repository: git init.

Працюй тільки над M1:
- мінімальний backend API, який віддає список локалей із готового locales.json;
- один тест, який перевіряє відповідь endpoint GET /locales;
- без UI, без пошуку, без авторизації.

Перед правками:
1. покажи план файлів;
2. назви критерій зупинки.

Після роботи, до commit:
3. покажи git diff і команду перевірки;
4. дочекайся мого "ок" і зроби маленький commit M1.

Зупинися після M1 і не переходь до M2 без мого підтвердження.

Якщо план M1 роздувся або поліз у сусідні теми, не запускайте реалізацію. Спочатку split: зменште M1 до одного перевірного фінішу, а решту винесіть у наступний milestone.

Порада щодо реалізації M1: тримайте дані в статичному locales.json, а сервер нехай просто читає файл і віддає його на GET /locales - без бази даних і зайвих шарів. Що тонший backend на цьому кроці, то менша поверхня для bugs і то простіше написати тест на форму відповіді.

Практика - етап 3

Останній крок - побудувати M2 і красиво передати роботу. Тут два prompts, і обидва ваші: спочатку план, потім handoff.

Порада щодо середовища: щоб fetch із браузера не вперся в CORS, найпростіше, коли Node віддає і статику frontend - тоді сторінка й API на одному origin (localhost:3000). Frontend, відкритий як file://, до API не достукається.

Спочатку придумайте prompts самі

Рамка: M2 - UI зі списком локалей з API M1, контракт M1 змінювати не можна, наприкінці потрібен HANDOFF_NOTE.md. Спочатку просіть план, файли не чіпати. Подумайте:

Наш варіант

Продовжимо до M2, але спочатку склади план, файли не чіпай.

M2:
- UI зі списком локалей з API M1;
- контракт API з M1 змінювати не можна;
- наприкінці потрібен HANDOFF_NOTE.md.

Назви файли під правку, ризик виходу за scope і як мені перевірити diff.

План чистий - дозволяєте M2 і просите записку; лізе не туди - /rewind або переплан без правок.

Склади HANDOFF_NOTE.md для свіжого reviewer.

Додай:
- goal;
- changed files і роль кожного файла;
- decisions;
- tests/checks з точними результатами;
- known risks;
- один next step.

Не включай усю історію чату.
Це не еталонне рішення, а один із варіантів. Робочий цикл довгого завдання один: план до коду, diff до довіри, summary до стиснення, handoff до передачі. Далі ви застосовуєте його вже на своїх завданнях.

UI-стартер для M2

Це необов'язкове доповнення до етапу 3. Щоб M2 виглядав не як "голий список із підручника", дайте Claude готовий зразок стилю й попросіть будувати UI на ньому. Це знову правило контексту зі слайда про порожній проєкт: дати зразок, а не "придумай красиво сам". Маленький starter задає tokens і пару класів - далі Claude повторює їх у таблиці локалей.

<!-- ui-starter.html: зразок стилю для M2 (структура + tokens, без JS).
     Прапори - flag-icons через CDN (fi fi-XX за ISO 3166), не з LLM -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons/css/flag-icons.min.css">
<style>
  :root { --bg:#0f172a; --card:#1e293b; --line:#334155; --text:#e2e8f0; --accent:#6366f1; }
  body { background:var(--bg); color:var(--text); font-family:system-ui,sans-serif; padding:24px; }
  .search { width:100%; margin-bottom:12px; padding:10px 14px; border-radius:10px;
            background:var(--card); color:var(--text); border:1px solid var(--line); }
  .locales { width:100%; border-collapse:collapse; }
  .locales th, .locales td { padding:10px 12px; text-align:left; border-bottom:1px solid var(--line); }
  .locales th { color:#8b98b1; font-size:13px; }
  .flag { font-size:20px; border-radius:3px; } /* масштаб через font-size */
  .code { font-family:ui-monospace,monospace; font-weight:700; color:var(--accent); }
</style>

<input class="search" placeholder="Пошук за кодом, країною або мовою">
<table class="locales">
  <!-- зразок рядка; живі рядки малює app.js із GET /locales -->
  <tr><th>Flag</th><th>Code</th><th>Language</th><th>Country</th><th>Currency</th><th>TLD</th></tr>
  <tr>
    <td><span class="fi fi-au flag"></span></td>
    <td class="code">en-AU</td><td>English</td><td>Australia</td><td>AUD</td><td>.au</td>
  </tr>
</table>

Starter - статичний зразок стилю без JS. У prompt просимо розкласти його на index.html і styles.css та додати тонкий app.js, який тягне список з API і малює рядки - так зразок перетворюється на живий UI, а структура залишається зрозумілою:

M2 - UI зі списком локалей з API M1. Starter - один файл ui-starter.html (додано):
статичний зразок стилю без JS, бери з нього tokens і класи, нових стилів не вигадуй.
Розклади UI на index.html + styles.css + тонкий app.js: app.js робить fetch GET /locales і малює рядки таблиці.
Прапори - з набору flag-icons за кодом ISO 3166 (class="fi fi-XX"), не з LLM.
Спочатку план файлів, контракт API не чіпай.
Красивий UI тут - побічний приз. Головне - ви потренували "дати зразок стилю як контекст" на видимому результаті.

Архітектура довідника цілком

Коли довідник зібраний, він складається в просту й зрозумілу картину - кожен шматок ви будували окремо:

flowchart LR DATA["locales.json"] --> API["Node API"] UI["UI: HTML, CSS, JS"] -->|"GET /locales"| API API -->|"список локалей"| UI

Дані лежать у locales.json (M1), Node віддає їх по HTTP, а frontend на чистому HTML, CSS і JS читає API і малює список (M2). Пошук і фільтр (M3) ви доробляєте самі на тому самому каркасі milestones. Один контракт API зв'язує всі шматки - на ньому й тримається зібраний довідник.

Уся архітектура - це дані, один API і тонкий frontend. Ніякої магії: довідник тримається на контракті між frontend і Node, а не на framework. Ця схема - не догма з першого дня, а стан після перевірених milestones: якщо M1 показав, що межа API неправильна, контракт краще поправити рано, ніж тягнути помилку далі.
Стек тут навмисно простий - так фокус залишається на процесі, а не на налаштуванні середовища. Але це не догма: розумієте, що робите - спокійно беріть свій (Express, React, що завгодно), milestones і контракт від інструментів не залежать.