Как и многие начинающие разработчики, я давно мечтал сделать свой первый pet‑проект — чтобы почувствовать себя «настоящим программистом» и перестать бояться собеседований. В итоге решился: буду писать веб‑приложение для личных заметок.
На самом деле я не совсем новичок. Раньше у меня уже были попытки освоить разные языки программирования, но дальше пары строчек кода дело редко заходило. Умение «гуглить правильно» и искать ответы на StackOverflow пока давалось тяжело, поэтому довести что‑то до результата было сложно.
В этот раз я решил пойти по трендам и подключить в процесс AI. Спасибо Хабру, что в нужный момент подкинул статью про Koda. С Koda и начался мой эксперимент. А команде хочу выразить респект за то, что всё бесплатно.
Что я хотел сделать
Идея была простой: сделать приложение для заметок, где текст можно красиво отображать на UI. Для разработки выбрал TypeScript: с его синтаксисом я уже немного знаком.
Как AI помогал
Опыт с Koda оказался полезным, хотя и не без нюансов.
Первые шаги
Сначала я спросил у AI, какие файлы и команды нужны для старта. Он сразу выдал готовый набор инструкций, и приятно удивило, что всё это можно было выполнить прямо в интерфейсе, не копаясь вручную.

Потом я уточнил: «Что дальше?» — и Koda нагенерил модель, сервис и файл для проверки. И, к моему удивлению, всё заработало с первого раза, именно так, как я ожидал.


Сгенерированные файлы
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Note.ts
export interface Note {
id: string;
title: string;
content: string;
createdAt: Date;
updatedAt: Date;
}
NoteService.ts
import { Note } from '../models/Note';
export class NoteService {
private notes: Note[] = [];
getAllNotes(): Note[] {
return this.notes;
}
getNoteById(id: string): Note | undefined {
return this.notes.find(note => note.id === id);
}
createNote(note: Omit<Note, 'id' | 'createdAt' | 'updatedAt'>): Note {
const newNote: Note = {
...note,
id: Date.now().toString(),
createdAt: new Date(),
updatedAt: new Date()
};
this.notes.push(newNote);
return newNote;
}
updateNote(id: string, updatedNote: Partial<Omit<Note, 'id' | 'createdAt'>>): Note | null {
const noteIndex = this.notes.findIndex(note => note.id === id);
if (noteIndex === -1) {
return null;
}
this.notes[noteIndex] = {
...this.notes[noteIndex],
...updatedNote,
updatedAt: new Date()
};
return this.notes[noteIndex];
}
deleteNote(id: string): boolean {
const initialLength = this.notes.length;
this.notes = this.notes.filter(note => note.id !== id);
return this.notes.length !== initialLength;
}
}
index.ts
import { NoteService } from './services/NoteService';
const noteService = new NoteService();
// Демонстрация работы с заметками
function main() {
// Создание заметки
const newNote = noteService.createNote({
title: 'Первая заметка',
content: 'Это содержимое моей первой заметки'
});
console.log('Создана заметка:', newNote);
// Создание ещё одной заметки
const secondNote = noteService.createNote({
title: 'Вторая заметка',
content: 'Текст второй заметки'
});
// Получение всех заметок
console.log('Все заметки:', noteService.getAllNotes());
// Обновление заметки
const updatedNote = noteService.updateNote(newNote.id, {
content: 'Обновлённое содержимое первой заметки'
});
console.log('Обновлённая заметка:', updatedNote);
// Удаление заметки
const isDeleted = noteService.deleteNote(secondNote.id);
console.log('Вторая заметка удалена:', isDeleted);
console.log('Оставшиеся заметки:', noteService.getAllNotes());
}
main();
Попытка с UI
Следующим шагом был интерфейс. Я снова обратился к AI — и вот тут начались приключения.

Первые варианты кода не работали, пришлось просить исправления.

После пары итераций UI всё же завёлся...

но радость быстро сменилась разочарованием: кнопка «Добавить заметку» не работала.
Тут и начался настоящий вайбкодинг: много экспериментов, вопросов к AI, несколько зависаний «агентного режима» и даже пара ложных догадок от самой модели. В чате поддержки подсказали, что часть из этого — известные баги, которые скоро должны поправить.

Сгенерированные файлы
ui.ts
import { NoteService } from './services/NoteService.js';
import { Note } from './models/Note.js';
export class NotesApp {
private noteService: NoteService;
private noteTitleInput!: HTMLInputElement;
private noteContentInput!: HTMLTextAreaElement;
private addNoteButton!: HTMLButtonElement;
private notesList!: HTMLDivElement;
constructor() {
this.noteService = new NoteService();
this.initializeElements();
this.attachEventListeners();
this.renderNotes();
}
private initializeElements() {
this.noteTitleInput = document.getElementById('noteTitle') as HTMLInputElement;
this.noteContentInput = document.getElementById('noteContent') as HTMLTextAreaElement;
this.addNoteButton = document.getElementById('addNoteBtn') as HTMLButtonElement;
this.notesList = document.getElementById('notesList') as HTMLDivElement;
}
private attachEventListeners() {
this.addNoteButton.addEventListener('click', () => this.addNote());
}
private addNote() {
const title = this.noteTitleInput.value.trim();
const content = this.noteContentInput.value.trim();
if (!title || !content) {
alert('Пожалуйста, заполните все поля');
return;
}
this.noteService.createNote({ title, content });
this.noteTitleInput.value = '';
this.noteContentInput.value = '';
this.renderNotes();
}
private deleteNote(id: string) {
this.noteService.deleteNote(id);
this.renderNotes();
}
private formatDate(date: Date): string {
return new Date(date).toLocaleString('ru-RU');
}
private renderNotes() {
const notes = this.noteService.getAllNotes();
this.notesList.innerHTML = '';
if (notes.length === 0) {
this.notesList.innerHTML = '<p>Заметок нет</p>';
return;
}
notes.forEach(note => {
const noteElement = document.createElement('div');
noteElement.className = 'note';
noteElement.innerHTML = `
<h3>${this.escapeHtml(note.title)}</h3>
<p>${this.escapeHtml(note.content)}</p>
<small>Создано: ${this.formatDate(note.createdAt)} | Обновлено: ${this.formatDate(note.updatedAt)}</small>
<button class="delete-btn" data-id="${note.id}">Удалить</button>
`;
this.notesList.appendChild(noteElement);
});
// Добавляем обработчики для кнопок удаления
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', (e) => {
const id = (e.target as HTMLButtonElement).getAttribute('data-id');
if (id) this.deleteNote(id);
});
});
}
// Метод для экранирования HTML, чтобы предотвратить XSS-атаки
private escapeHtml(text: string): string {
const map: Record<string, string> = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
}
// Инициализируем приложение при загрузке DOM
document.addEventListener('DOMContentLoaded', () => {
new NotesApp();
});
index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Приложение заметок</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Мои заметки</h1>
<div class="form-container">
<h2>Добавить новую заметку</h2>
<div class="form-group">
<label for="noteTitle">Заголовок:</label>
<input type="text" id="noteTitle" placeholder="Введите заголовок">
</div>
<div class="form-group">
<label for="noteContent">Содержимое:</label>
<textarea id="noteContent" placeholder="Введите содержимое заметки"></textarea>
</div>
<button id="addNoteBtn">Добавить заметку</button>
</div>
<div class="notes-container">
<h2>Список заметок</h2>
<div id="notesList"></div>
</div>
</div>
<script type="module" src="dist/ui.js"></script>
</body>
</html>
styles.css
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1, h2 {
color: #2c3e50;
}
.form-container {
margin-bottom: 30px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], textarea {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
textarea {
height: 100px;
}
button {
background-color: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
button:hover {
background-color: #2980b9;
}
.note-item {
border: 1px solid #eee;
padding: 15px;
margin-bottom: 15px;
border-radius: 5px;
position: relative;
}
.note-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.note-content {
margin-bottom: 15px;
}
.note-date {
font-size: 12px;
color: #777;
}
.delete-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: #e74c3c;
}
.delete-btn:hover {
background-color: #c0392b;
}
Истинная причина
В итоге ошибка оказалась не в логике приложения, а в конфигурации проекта.
Было:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Стало:
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
Как объяснил AI, CommonJS используется в Node.js и грузит модули синхронно через require, а ES2020 — это современная модульная система для браузеров, поддерживающая top‑level await. После этой правки всё заработало.

Логика работы кнопки «Удалить» и её расположение, конечно, потрясающее :-) Но уж исправлять я её не буду. По крайне мере не в этой статье.
Итоги вайбкодинга
Доступные и бесплатные инструменты для помощи в разработке есть уже сейчас
Качество генерации кода скорее радует, чем разочаровывает
Но вот поиск и объяснение ошибок «не в коде, а рядом с ним» у AI пока что вызывает настоящий отвал башки
Делитесь своим опытом работы с AI‑инструментами в комментариях, будет интересно почитать :-)
Комментарии (2)
Dhwtj
07.09.2025 13:32Пару лет назад пробовал TS
Примерно такие же проблемы. То есть на эти грабли вы бы вероятно наткнулись всё равно, LLM тут не виновата, просто не помогла
dan_sw
Это скорее минус, чем плюс (учитывая, что в программировании Вы новичок). С LLM-агентами программированием занимаются именно LLM-агенты, а не оператор :) Так гораздо сложнее научиться важным концепциям программирования, которые послужат фундаментом для дальнейшей карьеры программиста, просто потому что соблазн перекинуть всю интеллектуальную нагрузку на LLM-агентов велика.
Ключевое предложение тут "не копаясь вручную". Сейчас популяризируется мысль о том, что копание в программировании "вручную" (в т.ч. IDE, редакторах кода, инструментах разработки) - это уже устарело, да и не эффективно. Гораздо ведь проще переложить эту задачу на LLM, верно?
Такие моменты, на самом деле, и делают разработчика разработчиком. Если Вы новичок, то без самостоятельного разбирательства в том, как лучше обустраивать файловую структуру проекта, какие конфигурационные файлы в проекте используются (и как они используются) и т.д. - дальше новичка не уйти. Вдруг окажетесь в ситуации, когда кроме Vim'a или максимум VSCode (без LLM-агентов) ничего не будет, что тогда? Правильно, не сможете разобраться с чего начать.
Профессиональный программист (если он именно профессионал, эксперт, хакер) умеет развернуть проект в рамках своего стэка с нуля - будь то с использованием VS Code или Vim, без LLM-агентов. И лучше этому учиться сразу, без помощи LLM. В долгосрочной перспективе это даст свои плоды. В краткосрочной - LLM конечно всё это ускоряет, однако экспертом с LLM не стать. Для этого нужно самостоятельно преодолеть определённый путь (и он не лёгкий).
В целом, статья выглядит как просто реклама Kode, а это сообщение только это подчёркивает. Думаю моя догадка верна, но для читающих новичков, кои по своей неосторожности решились также начать программировать с "вайб-кодинга", думаю мой комментарий будет полезен. Сделаю вид, что статья не рекламная :)
А стоит, если хотите научиться программировать :)
Вывод: становитесь экспертом в программировании и не уповайте на "AI" :)
Вообще, Вы уже сейчас можете внести свой вклад в данный проект (чтобы он уж совсем не был LLM написан). Например, вот эта строчка может быть доработана (и дальнейшие с ней связки):
Тут нет обработки ситуаций, когда document.getElementById не нашёл элемент. В случае, если он не найдёт элемент он просто ничего не вернёт (кроме null), а его и приводить к конкретному типу HTML-элементов нет смысла. Это можно (и нужно) доработать. И дальнейшие "связки" тоже требуют доработки. Вот эта например:
Тут нет проверки на то, что this.addNoteButton вообще существует. Он может быть равен null (или undefined), и тогда вызов метода addEventListener вызовет ошибку (ведь null или undefined не может иметь методов). Хотя бы оператор ? использовать тут было бы хорошим тоном:
Да и зачем нужен этот самый оператор ? - это уже тема для разбора новичком :)
Такими вот подобными ошибками код пестрит, но в тоже время является неплохой возможностью для саморазвития как программиста :)
LLM/IDE with agents - являются неплохим инструментов для уже опытного разработчика, который повидал в своей карьере всякое. Для начинающего же (Junior-, Junior, Junior+) это скорее лишний отвлекающий фактор, чем что-то полезное. LLM лучше использовать не для программирования, а в качестве источника информации о программировании. Например, у него можно поинтересоваться какие проекты можно сделать и делать их потихоньку, запрашивать у него информацию по каким-либо концепциям и их реализовывать самостоятельно и периодически руководствоваться примерами программного кода на какую-либо тему. Это реально может помочь в быстром освоении каких-либо концепций из программирования.