Список — главное, план — способ его получить
Приложения для рецептов показывают чужую еду и хотят подписку. Заметки и списки в мессенджере не знают, что «500 г курицы» на вторник и «400 г курицы» на четверг — это одна упаковка на 900 г.
Sprig держит своё. Перетаскиваешь блюда по дням, ставишь число едоков — и список покупок собирается сам: одинаковые продукты складываются, граммы переходят в килограммы, строки раскладываются по отделам магазина. В магазине он работает без сети и догоняет сервер, когда связь вернётся.
Что внутри
Неделя лентой дней
День — секция, внутри обед и ужин с карточками блюд. Одна колонка одинаково работает за кухонным столом и в телефоне: под маленький экран ничего не переизобретается.
Перетаскивание своими руками
Хук на Pointer Events вместо библиотеки: работает и мышью, и пальцем — HTML5 drag-and-drop на телефоне попросту не существует. Тащат за ручку, у неё touch-action: none, поэтому страница под пальцем не едет.
Список выводится, а не хранится
Чистая функция от плана: «что готовим» и «что купить» физически не могут разъехаться. Несовместимые единицы показаны честно — «20 шт + 800 г», а не выдуманное одно число.
Отмена без диалогов
Документ недели живёт в редьюсере, прошлые состояния — в стопке, поэтому отмена и повтор получаются бесплатно. Вычёркивание в магазине в историю не идёт: покупка не правка документа.
Работает без сети
Устанавливается как приложение: оболочка в кэше service worker, правки ждут в очереди в localStorage и уходят, когда связь вернётся. Ни одно действие не блокируется.
Две зависимости
React и React DOM — больше во время выполнения ничего. Свой роутер на тридцать строк, свой CSS, сервер на Node 24 и node:sqlite без шага сборки. 21 тест на чистой логике.