Урок 6 из 8 · 25 мин · обновлено

Мини-приложение: логика, данные и проверка

Как довести трекер привычек до работающего приложения: сохранение отметок в браузере, счётчик дней, добавление привычек. Как проверять каждый шаг по сценариям и ловить пограничные случаи.

От страницы к приложению

Пока трекер — красивая страница: галочки ставятся, но после обновления пропадают. В этом уроке он станет приложением. План уже есть — из урока 3:

  1. Отметки сохраняются после обновления страницы.
  2. Счётчик дней рядом с каждой привычкой.
  3. Добавление своей привычки.

Делаем по одному шагу, проверяем, сохраняем копию.

Где живут данные

Чтобы отметки не пропадали, их нужно где-то хранить. У приложения в одном файле без сервера есть одно место — хранилище браузера (localStorage). Оно переживает обновление страницы и перезапуск браузера.

Но у него есть границы, о которых важно знать заранее:

  • данные живут в одном браузере на одном устройстве: на телефоне список будет свой;
  • если очистить данные браузера, отметки пропадут;
  • для общего списка на нескольких устройствах нужен сервер и учётные записи — это уже проект другого уровня.

Для личного трекера этого достаточно. Важно, чтобы вы сами понимали эти рамки и не обещали другим людям то, чего приложение не умеет.

Проверка по сценариям

Как понять, что шаг сделан? Не «на вид работает», а по сценариям: действие и ожидаемый результат. Для трёх шагов трекера:

Действие Ожидаемый результат
Отметить «Зарядку», обновить страницу галочка на месте, карточка зелёная
Снять галочку, обновить страницу галочки нет, счётчик уменьшился
Отметить привычку счётчик рядом стал на 1 больше
Ввести «Бег», нажать «Добавить» появилась карточка «Бег» со счётчиком 0
Обновить страницу после добавления «Бег» на месте
Нажать «Добавить» с пустым полем ничего не добавилось
Ввести только пробелы, нажать «Добавить» ничего не добавилось

Первые пять строк — обычные сценарии. Последние две — пограничные случаи: необычные, но возможные действия. Именно они ломаются чаще всего: нейросеть хорошо делает задуманный путь и легко забывает про странный.

Сценарии можно попросить у самой нейросети: «Составь таблицу проверок для этого шага: действие и ожидаемый результат, включая пограничные случаи». Но проходить их нужно руками — нейросеть ваш файл не запускает.

Шаг за шагом

Шаг 1 — сохранение. Запрос: «Сделай шаг 1 из плана: отметки сохраняются в хранилище браузера. Остальное не меняй. Пришли файл целиком». Проверьте первые две строки таблицы. Работает — сохраните копию tracker-2.html.

Шаг 2 — счётчик. Проверьте третью строку: отметили — счётчик вырос, сняли — уменьшился. Копия tracker-3.html.

Шаг 3 — добавление. Проверьте оставшиеся строки, особенно две последние. Если пробелы проходят, напишите: «Сейчас можно добавить привычку из одних пробелов — появляется пустая карточка. Не добавляй привычку, если после удаления пробелов по краям название пустое». Копия tracker-4.html.

Что ещё может пойти не так

Пара случаев, которые полезно проверить в любом приложении с данными:

  • Очень длинное название. Влезает ли карточка в экран телефона, не ломается ли вёрстка?
  • Повторное быстрое нажатие. Не появляются ли две одинаковые отметки за день?
  • Новый день. Утром галочки должны быть сняты, а счётчики — сохраниться. Проверить можно завтра или попросить нейросеть временно добавить для проверки кнопку «Сменить день» — и убрать её после.

Попробуйте сами. Доведите трекер до работающего приложения: три шага плана, проверка каждого по таблице сценариев, копия после каждого шага. Если застряли, сравните свой файл с примером ниже.

Пример готового трекера

Ваш код почти наверняка будет выглядеть иначе — нейросети пишут по-разному, и это нормально. Важно, чтобы он проходил все сценарии из таблицы. Один из рабочих вариантов:

HTML
<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Трекер привычек</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 480px; margin: 40px auto; padding: 0 16px; }
    .habit { display: flex; align-items: center; gap: 12px; padding: 12px; margin-bottom: 8px; border: 1px solid #ddd; border-radius: 12px; }
    .habit.done { background: #e8f5e9; border-color: #66bb6a; }
    .habit .name { flex: 1; }
    .habit .count { color: #777; font-size: 14px; }
    form { display: flex; gap: 8px; margin-top: 16px; }
    form input { flex: 1; padding: 8px; }
  </style>
</head>
<body>
  <h1>Мои привычки</h1>
  <p id="today"></p>
  <div id="list"></div>
  <form id="add-form">
    <input type="text" id="new-habit" placeholder="Новая привычка" required>
    <button type="submit">Добавить</button>
  </form>

  <script>
    // Сегодняшняя дата в виде 2026-10-20 — по местному времени
    const now = new Date();
    const today = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0');
    document.getElementById('today').textContent = 'Сегодня: ' + now.toLocaleDateString('ru-RU');

    // Привычки хранятся в браузере: название и даты, когда привычка выполнена
    let habits = JSON.parse(localStorage.getItem('habits')) || [
      { name: 'Зарядка', days: [] },
      { name: 'Читать 20 минут', days: [] },
      { name: 'Выпить 2 литра воды', days: [] }
    ];

    function save() {
      localStorage.setItem('habits', JSON.stringify(habits));
    }

    function render() {
      const list = document.getElementById('list');
      list.innerHTML = '';
      habits.forEach((habit, index) => {
        const row = document.createElement('label');
        row.className = habit.days.includes(today) ? 'habit done' : 'habit';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.checked = habit.days.includes(today);
        checkbox.addEventListener('change', () => toggle(index));

        const name = document.createElement('span');
        name.className = 'name';
        name.textContent = habit.name;

        const count = document.createElement('span');
        count.className = 'count';
        count.textContent = 'дней: ' + habit.days.length;

        row.append(checkbox, name, count);
        list.append(row);
      });
    }

    function toggle(index) {
      const days = habits[index].days;
      if (days.includes(today)) {
        habits[index].days = days.filter(day => day !== today);
      } else {
        days.push(today);
      }
      save();
      render();
    }

    document.getElementById('add-form').addEventListener('submit', event => {
      event.preventDefault();
      const input = document.getElementById('new-habit');
      const name = input.value.trim();
      if (!name) return;
      habits.push({ name: name, days: [] });
      input.value = '';
      save();
      render();
    });

    render();
  </script>
</body>
</html>

На что обратить внимание:

  • даты хранятся строками вида 2026-10-20 по местному времени — так отметка не «переедет» на соседний день после полуночи;
  • trim() убирает пробелы по краям, а проверка if (!name) return не даёт добавить пустую привычку;
  • название привычки выводится через textContent — почему это важно, разберём в следующем уроке.

Мини-тест: проверьте себя

Закрепите урок: несколько вопросов, разбор ответов — в конце.

  1. Где трекер хранит отметки?

  2. Что такое пограничный случай?

  3. Вы добавили привычку из одних пробелов, и появилась пустая карточка. Что это?

  4. Почему отметки с компьютера не видны на телефоне?