01Обзор проекта
Kyndsy — мобильное приложение, отвечающее на вопрос, на который почти никто не может ответить о себе сам: как я на самом деле говорю? Оно слышит голос владельца в обычном разговоре и считает грубые выражения, вежливые, слова-паразиты и моменты, когда тон становится резким. Показывает динамику по дням и неделям. И на этом останавливается — оно не говорит человеку, каким человеком его это делает.
Замысел в том, что приложения для саморазвития по большей части рисуют график вашего же мнения. Вы отчитываетесь о дне, они визуализируют отчёт. Kyndsy начинает с того, с чем сложнее спорить: со слов, которые действительно были сказаны, посчитанных, пока человек был занят жизнью, а не сел рефлексировать.
Это же делает продукт трудным. Считать речь — значит включить микрофон, а микрофон — значит, что человек должен доверять приложению больше, чем почти всему остальному в телефоне. Поэтому весь продукт построен вокруг обещания, которое обязано быть устройством, а не текстом: звук не сохраняется, слова не записываются, чужие голоса не разбираются, и нет учётной записи, к которой всё это можно было бы привязать.
Мы сделали всё: приложение для iOS и Android, содержимое на тридцати языках, лёгкий сервер, путь выпуска в магазины и сайт продукта. Ниже — задача, решения и то, что мы сознательно отказались делать.
02Контекст и задача
Приложение, которое слушает, — это область, где очевидная реализация одновременно неправильная. Разбор задачи дал набор ограничений, определивших всё дальнейшее.
- Доверие и есть продукт. У приложения, измеряющего речь, ровно один актив, и это не алгоритм. Если человек заподозрит, что звук уходит с телефона, точность не спасёт — он удалит приложение.
- Окружающие ни на что не соглашались. Микрофон в комнате слышит всех, кто в ней есть. Разбор речи людей вокруг владельца — это другое приложение, и куда худшее.
- Число о человеке опасно. Любая единая оценка, претендующая описать человека, — утверждение, которое продукт не может честно сделать; и её прочитают как приговор, что бы ни было написано рядом мелким шрифтом.
- Сырые счётчики врут. «Вы выругались девять раз» поощряет молчание: тот, кто ничего не говорил, выглядит как исправившийся.
- Слова неоднозначны. Одно и то же слово — шутка, цитата, жалоба на себя или настоящее оскорбление, в зависимости от тона и контекста. Счёт по строке даёт числа, в которых никто не узнаёт свой день.
- Люди говорят не на одном языке. Они переключаются посреди фразы. Счётчик, идущий только за одним языком, отчитается подозрительно спокойным днём каждый раз, когда владелец перешёл на другой.
- У магазинов есть правила про микрофон, про заявления о здоровье и психологии и про то, что вообще может закрывать подписка, — и проверяются они на подаче, а не в день запуска.
Всё это должно было работать на телефоне, в фоне, не съедая ни батарею, ни терпение человека.
03Цели проекта
- Считать речь на устройстве. Разбор идёт на телефоне; звук не сохраняется и не выгружается.
- Только голос владельца. Чужие голоса отбрасываются, а не разбираются, и режима «направить приложение на другого человека» не существует.
- Ни учётной записи, ни имени. Утекать нечему, потому что собирать нечего.
- Доли, а не сырые счётчики. Всё на тысячу сказанных слов, чтобы тихий день оставался тихим днём, а не достижением.
- Тридцать языков, включая смешанную речь — словари должны существовать во всех сразу, иначе число врёт тем, кто переключается.
- Различать оттенки. Ирония, сарказм, шутка и настоящая резкость разведены и помечены как догадка, а не как приговор.
- Полезно и без микрофона. Дневник, компас ценностей, тесты и вечерние вопросы работают сами по себе.
- Мотивация, которую нельзя обыграть. Серии, уровни и программы оцениваются измерением, а не открытием приложения.
- Никакой общей оценки человека. Ни в первой версии, ни потом.
04Что мы сделали
Приложение устроено как четыре места, каждое отвечает на свой вопрос, плюс то, что их держит:
- Сегодня — что только что было: показатель речи, цели дня и одна подсказка, а не список из шести.
- Прогресс — идёт ли это куда-нибудь: серии, уровни, программы и недельный отчёт.
- Компас — на что человек решил ориентироваться: три-пять ценностей, выбранных им самим, и движение к ним.
- Вы — всё, что принадлежит только ему: тесты самопроверки, семейный доступ, свои данные и поддержка.
Снизу лежит то, что делает эти четыре места честными: разбор речи на устройстве, словари и правила для тридцати языков, определение оттенков, модель счёта в долях, а не в итогах, лёгкий сервер, который почти ничего не знает, и реестр настроек, позволяющий менять продукт без выпуска в магазине.
05Приватность — это код, а не текст
Обещания приватности на сайте — не рекламный текст, который следующая оптимизация может тихо сделать неправдой. Это свойства сборки, и правка, делающая любое из них ложным, считается в проекте дефектом, а не разменом.
- Звук никуда не пишется — ни в файл, ни во временный буфер на диске, ни в журнал. И в отладочной сборке тоже, потому что именно так такие вещи и утекают.
- Разбирается только голос владельца. Чужая речь отбрасывается до того, как из неё получатся слова.
- Расшифровки нет. Приложение производит счётчики, а не текст; нет сохранённой фразы, которую можно было бы получить по запросу или случайно синхронизировать.
- Сессию запускает только человек. Ни автозапуска после перезагрузки, ни тихого возобновления в фоне.
- Голосовой профиль не покидает телефон и исключён из резервных копий — это биометрические данные, а копия как раз то место, о котором забывают.
- Серверу говорят четыре вещи и не больше: запрос настроек, разовая загрузка моделей, обращение в поддержку, если человек сам его написал, и чек подписки. Учётной записи нет.
- Человек — это пара случайных чисел, придуманных его же телефоном. Мы их не выдаём и не считаем — а значит, не знаем числа установок, и это была осознанная цена.
- Вход необязателен и ничего не открывает. Он нужен, чтобы перенести подписку и историю на новый телефон. Обязательный вход создал бы связь с настоящей учётной записью там, где её сейчас нет.
На последнем пункте стоит задержаться. Требование владельца было — знать только анонимный идентификатор, и соблазнительный способ его выполнить это обязательный вход, который мы затем обещаем не читать. Мы сделали наоборот: учётной записи нет вовсе, а необязательный вход читает из токена ровно одно поле и хранит его хешем.
06Что именно считает приложение
Четыре вещи: грубые выражения, вежливые выражения, слова-паразиты и поднятый, резкий тон. Всё это показывается на тысячу сказанных слов, а не сырым итогом: сырой счётчик делает молчание похожим на прогресс, а приложение, награждающее владельца за то, что он молчал, не поняло собственной работы.
Заголовок дня — индекс уважительной речи, и правила вокруг него продуманы не меньше самого числа:
- Он полностью скрыт, пока не услышано пятьдесят слов. Ноль в тихое утро читается как обвинение, а не как отсутствие данных.
- Дни, где слов меньше пятидесяти, показываются, но не считаются — видны пустыми, а не притворяются хорошим днём.
- Вежливость поднимает индекс не более чем на десять пунктов. Дальше показатель было бы дешевле набрать любезностями, чем заработать отсутствием резкости.
- Любое событие можно пометить как ошибочное, и это меняет число, а не просто вычёркивает строку из списка.
Виды поверх этого обычны по форме и внимательны в мелочах: недельный тренд, картина дня по часам, тридцатидневная сетка по квадрату на день. Часовой вид отказывается объявлять «худший час», пока у него нет десяти событий; тридцатидневная сетка оставляет тихие дни бежевыми, а не красит их в спокойные. Графики, уверенные на тонких данных, — это способ, которым продукт-измеритель теряет доверие читателя, обычно навсегда.
07Оттенки речи: ирония, шутка, укол
Шутка и оскорбление — не одно и то же слово дважды. Kyndsy отделяет иронию от настоящей резкости, шутку от укола, цитату от того, что человек имел в виду, — и показывает словами, что ему послышалось, рядом с событием.
Честность этой функции держат два правила. Первое: это догадка, а не приговор — приложение пишет «возможно, ирония» или «шутка — не засчитано» и показывает, на чём основывалось. Второе, и более важное: оттенок никогда не двигает индекс. Он сообщает человеку, но не подкручивает его показатель молча. Число, взвешенное по уверенности, никто не смог бы проверить, и оно быстро стало бы самой спорной функцией продукта.
Так же аккуратно устроен контекст: одно и то же слово считается по-разному, когда человек кого-то цитирует, говорит о себе или находится посреди спора. А поскольку ни один словарь не знает, что чьи-то слова значат лично для него, у человека есть свои списки.
08Тридцать языков — и смешанная речь
Приложение работает на тридцати языках — английский, испанский, португальский, немецкий, французский, итальянский, нидерландский, шведский, датский, норвежский, финский, польский, чешский, хорватский, румынский, венгерский, греческий, турецкий, русский, украинский, белорусский, иврит, арабский, хинди, индонезийский, вьетнамский, тайский, китайский, японский и корейский — и на нескольких сразу, потому что очень многие именно так и говорят.
Тридцать языков — это не тридцать переводов интерфейса. Это тридцать наборов словарей (грубые выражения, вежливые обороты, паразиты), и появляться они обязаны одновременно. Язык с недоделанным словарём не падает громко — он отчитывается спокойным днём. Человек видит хорошее число и верит ему, а это хуже сообщения об ошибке.
Один урок этого проекта выходит далеко за его пределы: признак, угаданный по виду текста, ломается на первом же языке с другой письменностью. Считать «оборотом» строку с пробелом работает ровно до японского, китайского и тайского, которые пишутся без пробелов, — и там приложение не находит ничего, молча, продолжая выглядеть работающим. Поведение языка должно браться из данных самого языка, и оно обязано быть закрыто тестом сразу по всем тридцати.
09Компас и дневник
Речь — только половина продукта. Вторая половина — то, на что человек решил ориентироваться: он выбирает три-пять ценностей из семи (уважение, честность, самообладание, ответственность, забота, справедливость, надёжность), и приложение показывает движение к ним, а не к чьему-то чужому представлению о хорошем человеке.
Движение складывается из двух источников: измерений речи и собственного дневника поступков — сдержал обещание, извинился, сорвался, исправил. Плюс три вопроса в конце дня, когда они нужны.
Общей оценки нравственности нет — ни сейчас, ни потом. Это единственное, что продукт отказывается вычислять. И именно поэтому компас работает: честные ценности человек выберет только для приложения, которое не собирается ставить ему за них отметку.
Всё в этой половине работает без микрофона. Дневник, компас, тесты и вечерние вопросы стоят сами по себе, так что голосовую часть можно настроить позже или никогда — и приложение всё равно останется полезным. Это продуктовое решение с настоящими последствиями для удержания, и мы приняли бы его снова: запрос разрешения на первом экране — то место, где приложение о приватности теряет тех, ради кого его писали.
10Программы, серии и недельный отчёт
Мотивация здесь обязана была быть невосприимчивой к двум привычным обходам: часто открывать приложение и ничего не говорить. Поэтому:
- Программы идут от 7 до 30 дней с одной целью и одним числом — неделя без грубых слов, мягче отвечать две недели, меньше паразитов за месяц. Сработало или нет, решают измерения, а не галочка.
- Серия считает дни, в которые человек действительно удержал уровень. Тихие дни её не рвут и не продлевают — убрать телефон не способ выиграть.
- Уровни зарабатываются днями, а не открытиями приложения: Наблюдение, Контроль, Спокойная речь.
- Щиты закрывают срыв, но не бесконечно, а сорванный день можно выкупить — только серия из тридцати дней, четыре из которых куплены, это не серия из тридцати дней, поэтому такие дни считаются и показываются отдельно.
- Недельный отчёт прямо говорит, когда сравнивать не с чем. Сводка, в которой всегда что-то улучшилось, перестаёт читаться к третьей неделе.
Стоит назвать одну намеренную асимметрию: бесплатный уровень ограничивает показ истории, а не счёт. Считать серию только по видимому окну значило бы показывать тридцатидневную серию как семидневную — это не ограничение бесплатного тарифа, а враньё про человека.
11Сто тестов самопроверки
Рядом с измерениями лежит сотня структурированных тестов самопроверки: по двадцать пять утверждений, около четырёх минут, пять граней оцениваются отдельно, поэтому ответ никогда не сводится к одному плоскому числу. Темы — речь, чувства, близкие, работа, вы сами, привычки и жизнь в сети.
Это явным образом не диагнозы и не измерения личности — это структурированная саморефлексия, и приложение говорит об этом там, где человек прочтёт, а не в настройках. Ответы остаются на телефоне, как и всё остальное.
Инженерный смысл здесь в том, что сотню тестов нельзя вести как сотню написанных руками экранов. Они лежат ресурсом, а не кодом: собираются из исходников, проверяются на сборке и отвергаются, если у теста нет полного набора утверждений и граней. Именно эта проверка делает возможным иметь их сто, и она же позволила раздать правила открытия единообразно, а не расставить руками.
Половина открывается трудом, половина покупается. Товаров под платную половину намеренно немного, а не по одному на тест: сотню позиций в магазинах никто не заведёт руками и никто не переведёт на тридцать языков. Продаётся открытие; какой именно тест на него потратить, человек выбирает в приложении уже после подтверждения покупки.
12Подписка, семья и магазины
Платный уровень — подписка, и семья её делит: шесть мест на год, включая место покупателя. Важно, что именно делит семья и чего не делит: семья делит право, а не данные. Никто не видит чужих чисел, событий и дневника, а тот, кто оплатил, видит свободные места и даты, а не то, кто их занял.
Это не оговорка мелким шрифтом, а устройство: чужих показателей на сервере нет вовсе, поэтому в семейном экране нечего случайно раскрыть. Вводя код, человек соглашается разделить подписку, а не назваться.
Два правила из этой части пережили саму функцию. Первое: подписка и семейное право — один флаг в приложении; второй вопрос вида «а может, он в семье» забыли бы ровно там, где он важен. Второе: молчание сервера право не снимает — сеть пропадает намного чаще, чем кончается подписка, и приложение, запирающее платящего человека в поезде, сделало очевидный выбор плохо.
Правила магазинов определили здесь многое: что вправе утверждать приложение с микрофоном, как можно показывать платный уровень и то, что сломанную сборку не откатишь хотфиксом. Поэтому политика версий — минимальная поддерживаемая версия, требование обновиться, режим обслуживания и текст сообщения на всех языках проекта сразу — это серверная настройка, а не то, что вкомпилировано внутрь.
13Архитектура: приложение, сервер, реестр
Архитектура следует из обещания приватности: как можно больше на телефоне, как можно меньше на сервере.
- Приложение ведёт разбор, хранит историю и владеет голосовым профилем. Оно и есть продукт; сервер здесь на подхвате.
- Лёгкий сервер отвечает на четыре вида запросов — настройки, разовая загрузка моделей, обращения в поддержку и чеки подписки. Таблицы пользователей у него нет, потому что нет пользователей, которых туда класть.
- Реестр настроек держит то, что иначе было бы зашито в приложение: идентификатор пакета, адрес API, параметры входа, список языков, флаги функций и политику версий. Значения не пишутся в исходники руками, а генерируются в них, и сгенерированный файл помечен «не править» по простой причине: ручная правка теряется при следующей генерации.
- Предполётная проверка стоит гейтом перед каждой сборкой: она отказывает, если идентификатор пакета разошёлся с реестром, если версия не выше собранной, если сервер приложения не отвечает, если словарь неполон хоть на одном языке проекта, если статический анализ не чист, если падают тесты или если в исходниках нашлось похожее на ключ. Она возвращает ненулевой код, поэтому годится гейтом в любом CI.
- Платный уровень стоит за флагом, который гасится без пересборки и без новой версии в магазине.
Сайт — небольшое PHP-приложение без собственной базы, кроме переписки поддержки: тот же принцип, применённый на уровень выше.
14Технологический стек
- Flutter для iOS и Android из одного кода — с дизайн-системой в коде, чтобы цвет, отступ и скругление задавались в одном месте, а не у каждого виджета.
- Распознавание речи и разбор на устройстве: модели скачиваются один раз и работают локально, а манифест с их размерами и хешами строится из файлов, а не пишется руками.
- PHP 8.4 для сервера приложения и сайта, с небольшой базой только под переписку поддержки.
- Реестр настроек как источник истины для параметров приложения, с генерацией кода и предполётным гейтом.
- Тридцать языковых наборов в приложении и три на сайте, с паритетом словарей, который держат тесты, а не ревью.
- Строгая политика безопасности содержимого на сайте — ни одного инлайнового стиля, вычисляемые значения собираются в отдельную таблицу стилей, шрифт свой: страница про приватность не тянет ни одного чужого ресурса.
15Дизайн и UX
Визуальный язык — одна тёмная тема, снятая с сайта продукта, чтобы человек открывал ровно то, что ему обещали. Светлый вариант написан в токенах и намеренно не подключён: две темы означают два набора снимков и вдвое больше мест, где вёрстка ломается незаметно.
Цвет, отступ и скругление живут в одном файле дизайна, а переход из тревожного цвета в спокойный идёт через тёплый жёлтый: напрямую середина даёт оливковую грязь — то, что выглядит небрежностью, а на деле является пропущенной опорной точкой.
У приложения есть персонаж, и правила вокруг него не столько про обаяние, сколько про честность: настроение читается силуэтом, а не только цветом, поэтому дальтоник видит то же, что все; настроение — непрерывная шкала с анимацией между значениями, а не пять готовых картинок, потому что скачок читается как другая картинка, а не как тот же герой; а при тревоге брови идут внутренними концами вверх, потому что опущенные внутрь — это злость на того, кто смотрит.
Во всём остальном интерфейс следует главному правилу продукта: показывать измеренное, говорить вслух, когда сказать нечего, и никогда не сводить человека к одному числу.
16Как мы работали
- Сначала обещание. Мы записали, чего приложение не будет делать никогда — хранить звук, разбирать окружающих, спрашивать, кто вы, оценивать вас как человека, — до всякого проектирования: эти решения ограничивают архитектуру, а не следуют из неё.
- Ядро измерения. Разбор на устройстве, счёт в долях, индекс с его порогами и отказом появляться на тонких данных.
- Языковая работа. Тридцать словарей и правил, с паритетом, закрытым тестами сразу по всем.
- Вторая половина. Компас, дневник, программы, серии, недельный отчёт — всё, что работает без микрофона.
- Тесты как ресурс. Сотня тестов самопроверки, собираемых и проверяемых, с лестницей открытия, которую раздаёт сборщик.
- Монетизация. Подписка, семья, небольшой набор товаров в магазинах и платный уровень за флагом.
- Путь выпуска. Реестр, генерируемая конфигурация, предполётный гейт, регистрация сборок и политика версий.
- Сайт. Три языка, строгая политика безопасности содержимого и лендинг, показывающий настоящие экраны приложения.
Две вещи возвращались по ходу работы постоянно, и обе стали правилами. Всё, что интерфейс утверждает о поведении человека, обязано проверяться измерением; всё, что приложение обещает о приватности, обязано держаться кодом, а не заметкой в документе — договорённость живёт ровно до следующего, кто её не прочитал.
17Результат
Получился измерительный продукт для того, о чём обычно только догадываются, — устроенный так, что измерение не стоит человеку приватности.
- Разбор речи на устройстве: ничего не записывается, ничего не расшифровывается, чужие голоса отбрасываются.
- Всё показывается на тысячу слов, поэтому молчание не принимают за прогресс.
- Ирония, сарказм и шутки разведены — и помечены как догадка, которая не двигает показатель.
- Тридцать языков со словарями, появляющимися одновременно, и тестами сразу по всем.
- Компас ценностей, дневник, программы и недельный отчёт, который признаёт, когда сравнивать не с чем.
- Сотня тестов как проверяемые ресурсы и небольшой поддерживаемый набор товаров под платную половину.
- Семейный тариф, который делит подписку и ничего больше — потому что данных на сервере нет.
18Выводы
Kyndsy — проект, где самая трудная инженерия была в ограничениях. Разбор на устройстве, отсутствие расшифровки, отсутствие учётной записи, отказ от общей оценки, отказ разбирать окружающих — каждое из них усложняло сборку и делало продукт возможным. Версия этого приложения с серверным конвейером и логином писалась бы быстрее и не заслуживала бы ничьего микрофона.
Урок, который мы уносим, — о том, как держатся обещания: гарантия, живущая в документе, — гарантия ровно до следующей правки, которую с ней не сверили. Здесь выдержали те, что выражены кодом и закрыты тестами: паритет по тридцати языкам, контраст, проверяемый числом, а не взглядом, движение, проверяемое значениями, а не снимком, и гейт сборки, который отказывает, а не предупреждает.
Если вы делаете мобильный продукт, где приватность — это функция, а не оговорка; что-то, что обязано работать сразу на многих языках; или приложение, числам которого люди должны верить о себе, — такую работу мы ведём от замысла до магазина.
