Стендап Сьогодні
Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті
Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni
02.02.2025
Рік з фітнес-кільцем Oura
Вже більше ріку ношу фітнес-кільце Oura, про яке вже писав. Все ще можу порекомендувати його всім. Звісно, для “серйозного” спорту кільце ніколи не зрівняється з браслетом чи годинником — в ньому немає GPS, екрану, сповіщень, і так далі. Втім, не всі ми “серйозні” спортсмени.
Все, що є — це плюс-мінус такий саме набір датчиків, як в тому ж еплвотчі чи будь-якому браслеті: акселерометр, пульс температура, кисень. А різниця в тому, що Oura робить з даними тих датчиків.
А саме, Oura будує цілісну картину самопочуття. Так би мовити, похідну, а то й другу похідну від базових показників. Наприклад, є “кількість сну” - як і всюди, базові дані. Але також є “балансу кількості” - це те, як часто ти висипаєшся. А потім з балансу та ще декількох показників — пульсу, часу засинання — складається загальна оцінка. А зі стабільності всіх оцінок разом є ще вищий показник - “стійкість” - він поступово зростає та падає залежно від способу життя.
Також в Oura практично немає звичної гейміфікації — але наявність цих похідних показників реально мотивує стежити за собою та робити якісний сон та фізичну активність звичкою, а не випадком.
Дуже подобається мова, якою спілкується Oura - вона не така бадьоро-позитивна, як завжди, а дійсно дружня. Якщо ти гарно відновлюєшся — вона підбадьорює. Коли наступає чорна смуга — співчуває та пропонує відпочити. Мені в житті цього не вистачало.
На кінець — цікава історія. Весь січень Oura мене хвалили за якісний сон та відновлення. Але насправді я не дуже й намагався вчасно лягати або хоч щось змінити зі сном. Зате — почав регулярно робити інтенсивні тренування. Та це абсолютно вплинуло на самопочуття так, як ніщо інше. Тобто кільце хоч і помилялося, та й допомогло мені звернути на це увагу. 🫀
01.02.2025
Робота з iCloud Drive та резервне копіювання
🛟 Сьогодні намагався (та зробив!) виконати ще одне прохання користувачів: додати резервне копіювання бази (таймтрекеру.) Функція очевидна, та ще й врятує мене, якщо раптом випущу збірку, що псує дані. Звісно, як місце для збереження копій я обрав iCloud Drive. Бо він вже доступний для всіх без зайвої авторизації. Отже.
З погляду застосунку, iCloud це майже звичайна файлова система. На iOS найскладніша на моїй памʼяті модель роботи з файлами — доступ порізаний на окремі теки, все відбувається через URL тощо. Але принаймні, коли ти вже знаєш URL контейнера, то далі все як звичайно.
А от процес налаштування заплутаний та погано задокументований. Ось стаття, з якою все нарешті вийшло. Спочатку ми оголошуємо “повсюдний контейнер” - це як скибка сховища користувача, яка буде належати нашому застосунку (та яку він буде бачити). Контейнер існує окремо від застосунку, та навіть декілька застосунків можуть ділити один контейнер.
Потім… потім воно працює з коду (ну, не на симуляторі, якщо тільки не увійти там у свій iCloud). Але в iCloud Drive файлів я не бачив. Було важко зрозуміти — що саме не так. Для iCloud Drive цей повсюдний контейнер потрібно додатково оголосити публічним та призначити йому імʼя - після чого… ні, все ще нічого не відбувається.
З вище вказаної статті збагнув, що щоб файли зʼявилися у Drive, вони повинні бути в піддиректорії Documents
, а не прямо в контейнері. Тоді… ще потрібно було збільшити версію збірки, і ось нарешті тека з файлом зʼявилася і в Files на айфоні, і невдовзі на макбуці теж. 😮💨
Решта - справа техніки; експорт у ZIP в мене давно вже є. Трохи порефакторив, та додав виклик на відкриття застосунку: якщо з попереднього експорту минув день, створюємо ще один. Можна ще потім додати чистку старих файлів, але то менш критично - мій експорт майже за рік займає 277 Кб.
31.01.2025
Ping - сайт
Як і планував, нова версія вже у TestFlight. Також нарешті дістався до створення сторінки для застосунку: ping.leonid.codes. Поки сторінка майже порожня, але на з цим продуктом є стимул її розвивати.
Саму сторінку я скопіював із останньої такої задачі. Взагалі гарно б було натренуватись робити мінісайти для (менших) проєктів, бо інакше вони часто залишаються без всякого маркетингу.
А маркетингу воно знаєте, скільки потрібно? Ще б дорожню карту кудись викласти, та сторінку в соціальних мережах вести, та хоч мінімальний, але набір статей з поясненнями. Та відео використання. В мене традиційно немає на то хисту, а коли і є хист, то немає часу.
Про час: відзначу, що розгортування на Cloudflare Pages зайняло лише пару хвилин. Це з урахуванням того, що мій домен вже під Cloudflare. Бо завдяки цьому він сам робить всі налаштування, достатньо підʼєднати репозиторій та вказати піддомен.
30.01.2025
Завтра випускаю нову збірку трекера. Зміни доторкнулися головної частини — алгоритму пропонування тегів. Частково повернув ту логіку, що була до міграції на SQLite, а також додав нові круті речі.
Для початку, зробив, щоб по змахуванню модала редагування він зберігався. Раніше скасовувався. Скасувати все ще можна — кнопкою — але збереження набагато частіша дія. (Ну та й звісно, якщо нічого не змінювати, то нічого й не збережеться.) Також повернув функцію “скопіювати з минулого”, яка зникла після редизайну.
Але головне — це покращений алгоритм пропонування. До того він спирався тільки на час (тобто пропонувалися теги, що були відмічені раніше в ту саму годину та день тижня.) Бо це просто легше було запрограмувати на SQL. Зараз, якщо вже є обрані теги, то я вибираю з бази суміжні з ними. До речі, хоч через це підбірка тегів постійно змінюється, завдяки анімації наочно зрозуміло, що пішло та що додалося — тому ти не губишся в цих змінах.
Я почав з того, що витягував суміжні теги до кожного обраного, та поєднував за частотою. Але це дає дивні результати; умовно якщо обрати теги desktop, coding
, то в пропозиціях будуть gaming
як суміжний із desktop
та laptop
(!) із coding
.
Тому треба було знаходити теги, суміжні до всіх обраних відразу. Що не так легко, бо тегування зберігаються в табличці tagging
- знайти суміжний до одного тегу це один join
, але якщо до декількох — не плодити ж джойни? Винайшов чудове рішення, що знаходить всі пінги, де зустрічалися всі обрані теги:
SELECT ts, count(*) cnt
FROM tagging
WHERE tagId IN (1,2,3)
GROUP BY ts
HAVING cnt = 3
Залишається результат заджойнити назад до tagging
та вибрати всі теги, що збігаються за часом відмітки.
PS. Після роботи дуже важко перемкнутися на власний проєкт. Голові потрібно відпочити, а коли відпочинеш — вже пізно щось починати. Тому я для себе знайшов, що краще займатися власним проєктом першим чином рано вранці. Тоді до початку роботи встигаю відпочити.
29.01.2025
SQLite та компоненти дат
Коли збираєшся обчислювати статистику за тижнями, стаєш перед неуникним фактом, що в Америці тижні рахують з неділі, а не з понеділка. Як не крути, а доведеться підтримувати обидва варіанти. Поки що я елегантно про це “забув” та рахував як-небудь. Але прийшов час це виправити.
Все ще й ускладнилось тим, що в SQLite якось дивно розподілені команди форматування: в старих версіях є тільки %W
- номер тижня, що починається з понеділка, та %w
- номер дня, що починається з неділі. А в нових додали протилежні ним %U
та %u
. Та той SQLite, що є на iOS, ще старий.
В GRDB є можливість скомпілювати власну, нову версію SQLite. Але в цього підходу не все гладко із залежностями, тому мені довелося відмовитись.
Натомість просто додав до пінгів стовпчики weekCode
, weekday
та ще парочку. Оскільки дата не змінюється, то залишається один раз їх заповнити — та можна забути про всі ті запити із strftime
.
Що таке weekCode
? Комбінація року та номеру тижня: 202451
. Тут, до речі, дізнався ще про один нюанс — що звичайного %Y
тут недостатньо, бо тиждень 202501
почався 30 грудня 2024. Тобто потрібний “рік, до якого призначено цей тиждень”, та у Swift навіть так і називається: DateComponents.yearForWeekOfYear
.
Та, до речі, всі ці обчислення дат я тепер роблю у Swift, а там календар знає всі налаштування користувача — і перший день тижня, і часовий пояс.
28.01.2025
Фронтенд — найсерйозніше програмування
Здається, що серед бекендерів панує переконання, що бекенд — це серйозна, складна архітектура, а фронтенд — це щось таке дилетантське. Тому, певно, на фронтенді постійно немає ладу, фреймворки якісь недороблені, повний хаос.
Я зі свого досвіду з цим не згодний; навпаки, вважаю, що програмування графічних інтерфейсів — це найскладніша із сучасних задач. Проілюструю тим, що навіть компанії, які здатні підтримувати мільярди користувачів, або навіть хмарні гіганти, які є фундаментом тисяч інших продуктів, все одно мають застосунки з недолугими інтерфейсами.
На мою думку, причиною є те, що графічні інтерфейси ростуть не в розмірі, а в складності. Навіть найпростіші підзадачі мають крайові випадки: zalgo. Навіть найпростіші інтерфейси викликають комбінаторний вибух параметрів. Стандартизація та DRY часто роблять тільки гірше.
Стосовно конкретно вебінтерфейсів, тут взагалі утворилась пекельна суміш обставин: засоби, які історично задумувалися для іншого, а також ніяких обмежень на розміри вікна, та відразу купа браузерів та пристроїв, на яких той вебінтерфейс будуть дивитися. Немає нічого дивного, що після контрольованого середовища бекенду все це крутить голову.
Так що фронтенд вартий поваги та уваги, та краще розвивати власну майстерність — а не зневажати його як другосортне програмування.
27.01.2025
Віддалений Docker (та ще й на Windows)
Для продовження моїх експериментів з нейронними мережами доведеться запускати багато всього на компʼютері з Windows. Згадав, що в Docker є “віддалений контекст” роботи, та розібрався з ним.
В мене так склалося, що Docker або на локальній машині, або десь в хмарі прихований за абстракцією. Ну або як мінімум десь на віддаленій машині, куди я заходжу по SSH. А віддалений контекст — то коли локальна команда docker
керує рушієм, що запущений на іншій машині.
Якщо нічого не боятися, то можна просто відкрити підключення до Docker мережею. Але в такого рішення немає ніякої авторизації взагалі. Для автентифікації можна нагенерувати сертифікатів TLS. Проте то нудно.
Найпростішим буде тунелювати до Docker через SSH. У Windows є сервер OpenSSH - як додатковий компонент, який потрібно встановити, а потім увімкнути його сервіс. Далі можна підʼєднуватися до компʼютера за паролем — або додати SSH-ключ - майже як вдома!
Вдома, але не зовсім… З оболонкою Windows не хочеться мати справи. На щастя, для віддаленого контексту Docker це не перешкода, та працює він чудово. Спочатку створюємо конфігурацію командою docker context create
, обираємо командою docker context use
, та всі команди Docker будуть передані із локального середовища на виконання віддаленій машині. Для мене це просто магія! 🧙♂️
Єдиний нюанс — монтувати теки можна тільки ті, що на віддаленій машині. Що логічно, але трохи незручно. А контекст збірки береться з локальної машини (тобто можна копіювати потрібні файли в образ.)
26.01.2025
SearXNG - локальний метарушій для пошуку в інтернеті
Встановив собі сьогодні SearXNG. Це такий вебзастосунок, який збирає результати пошуку з всіляких рушіїв та показує в типовому інтерфейсі. Щось на кшталт DuckDuckGo чи Kagi, тільки відкрите. Ось, при написанні цього поста тільки ним та й користувався.
В нього є готовий до використання образ Docker та ніяких обовʼязкових залежностей, тож випробувати дуже легко. Можна встановити його на сервер (та і є багато публічних серверів), але мене цікавив саме локальний запуск — так я більше впевнений, що його не заблокують. Ресурсів витрачає небагато (600 Мб памʼяті.) Відпрацьовує в межах пари секунд.
Мені, головним чином, потрібні налаштування пошуку під себе. В SearXNG можна: обирати набір рушіїв за замовчуванням та підіймати чи опускати домени у видачі.
Можна й створювати власні рушії; як повноцінно на Python, так і через конфігурацію (ось, спробував зробити для Прому). Що відрізняє SearXNG від того ж DDG - вибір рушія з !prom
видає результати в інтерфейсі SearXNG
, а для переходу безпосередньо на пошук на сайті потрібно писати !!prom
.
А ще, теоретично, із запуском локально можна зробити рушій, який буде шукати в моїй базі Obsidian, та частково закрити питання згадування минулих досліджень - тобто щоб результати з Obsidian зʼявлялися прямо на сторінці пошуку.
25.01.2025
Терміни та пріоритети
Зізнаюся, що я декілька років був чітко проти обох, мотивуючи це тим, що все це не працює — терміни спливають, задачі не виконуються. А натомість потрібно все робити якнайскоріше, та тоді ніякі терміни не потрібні.
Втім, нарешті починаю розуміти, що не працюють терміни для мене, через наявність деякого ступеня часової сліпоти, тобто вади, де вагомість майбутнього наближається до нуля. З часовою сліпотою робляться ті задачі, що термінові вже зараз. Або ж робиться все, що завгодно, бо терміни не враховуються у вибір того, чим зайнятись. Як бачите, дуже близько до моєї раціоналізації.
Прочитав, що щоб уникнути часової сліпоти, терміни не просто необхідні — вони також повинні бути візуальними. Мало бачити дату — потрібно бачити перебіг часу до цієї дати. Цього мало бачив в різних програмах — максимум “залишилося N днів” (що вже краще, ніж тільки дата). У своєму застосунку я намагаюся ілюструвати це смужкою прогресу від дати створення проєкту до кінцевої дати. (Окрім того, досить важливо бачити, скільки вже зроблено та скільки залишилося зробити, але поки не придумав, як.)
А також, що я розумію тепер, це терміни потрібні, щоб зрозуміти, чого я робити не буду. Попросту, якщо термін вибіг, а проєкт все висить — можна його знімати. Та тут стають до нагоди пріоритети: бо, звісно, не з кожним проєктом так. Але знімати неважливі проєкти життєво необхідно, особливо з моїм апетитом їх починати. Це те, що відомо під назвою матриці Ейзенхауера, але мені завжди важко було зрозуміти, що термінове, а що ні.
24.01.2025
Чому кукі безпечніше?
🥠 Кукі були потрібні до епохи JavaScript, коли серверу потрібно було якось зберігати дані на клієнті. Звісно, зараз у нас на клієнті повноцінні застосунки, тож кому ті архаїчні кукі потрібні?
Втім, насправді в кукі вбудовано багато механізмів захисту, та не всі з них ми взагалі можемо відтворити. Головним чином, це атрибут HttpOnly - кукі з HttpOnly
не доступні для клієнтського коду, їх наче немає. А значить, їх неможливо вкрасти ніякою інʼєкцією коду. Що, до речі, чи не найбільш реалістична атака на ваш сайт.
Окрім того, в кук є ще низка атрибутів, яка допомагає захистити їх від помилок - CSRF, пересилання без шифрування тощо. Одним словом, безпека кук — це функція браузера, та нам не потрібно (та не варто) придумувати власні альтернативи.
(Примітка: вкрасти куки через злам браузера або локальний доступ до нього — звісно, можливо. Тільки це не так ймовірно, як інʼєкція.)
До речі, fetch()
підтримує кукі, причому опціонально навіть якщо API не на тому ж домені. Єдине, що хтось з того домену повинен куку встановити. На відміну від саморобних токенів, кукі також надсилаються для зображень та інших ресурсів.
По змісту в кук немає різниці з будь-якими іншими токенами — туди можна записати будь-що. Але для автентифікації можна виділити два варіанти: або це випадковий токен, що вказує на стан на сервері, або токен зі змістом.
Мені подобається зберігати стан на сервері, бо це робить кукі повністю непрозорими та не потребує складних механізмів приховування та верифікації змісту. Та й розмір стану на сервері практично не обмежений. (Ось відповідь на Stack Exchange, де про це розповідає.)