Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

12.01.2025

Браузер, який я хочу

🌐 Ділюся ідеєю браузера, яка в мене крутиться в голові декілька місяців (десь після переходу на Obsidian Canvas)… але, зрозуміло, поки не вистачає часу спробувати.

💭 Я хочу браузер, інтегрований в базу знань. Та орієнтований на задачі. Я боюся сказати “дослідження”, бо це зараз має свої конотації зі всілякими zettelkasten та Roam Research, тобто з побудовою графа знань, а мені таке зазвичай не потрібно.

🕸️ …Мені потрібно: знайти трекові світильники, перенести сайт на Cloudflare та розказати про браузери. Кожна така задача тягне за собою низку питань, а відповідно — пошуків в інтернеті. От хочу бачити сторінки в контексті задачі. Причому щоб історія теж автоматично привʼязувалася.

📚 Бо зараз доводиться вручну переносити знахідки в базу, а це призводить до того, що тільки меншість досліджень зберігаються на майбутнє. Кожен раз обираєш: чи знадобиться це потім, чи задача на один раз. Та тут друга ідея інтеграції: пошук можна робити не тільки по вебу, а й по базі знань та по контекстній історії. Це автоматично розвʼязало б проблему зберігання на потім.

⚙️ В цілому, ідея більш реальна, ніж здається (я ще в школі робив “браузери”, бо в нас блокували Internet Explorer - одна компонента на Delphi та кілька кнопок це все, що потрібно.) Зараз, мабуть, можна привʼязатися до Electron, чи до WebKit. Але часу немає, тож поки тільки можу фантазувати.

📌 Цікаві поробки в цьому напрямку: SenseMap. Horse browser. Perplexity.AI. Буду радий дізнатись ще.


11.01.2025

Мʼясне сортування

В мене в застосунку для GTD є режим FVP для простого вибору наступної задачі. Він був трошки напівавтоматичний, а саме: переглядати задачі в послідовності, та не стрибати туди-сюди, доводилося вручну — за звичайним списком. Тобто все одно перед очима був повний список.

(Нагадаю, що весь сенс алгоритму FVP - це такий класичний пошук найпріоритетнішої задачі в масиві — запамʼятовуємо першу задачу, перебираємо всі інші та порівнюємо з нею, як найдемо більш приорітетну — запамʼятовуємо її та йдемо далі. Ну всі такий код писали. Але тут воно працює “на мʼясі”.)

👈 Сьогодні, щоб полегшити собі життя, впровадив спеціальний режим, де показуються тільки дві задачі - “найважливіша” та наступна з не перебраних, Залишається тільки обрати між ними (і так N разів). Такий собі “тіндер для задач” замість списку. 👉

Насправді колись я робив цілий сортувальник в такому стилі. Але для FVP потрібно тільки обрати одну задачу, та “мʼясна складність” алгоритму лише O(N).


10.01.2025

Спостереження за станом збірок на GitHub Actions

Маю таку проблему, що коли працюю з купою гілок на GitHub (тобто майже завжди), то важко встежити за результатами збірок — які можуть тривати до 20 хвилин. Хотів зробити віджет, який мені б сповіщав про завершення моїх збірок.

Інтерфейсна частина вирішується дуже легко з xbar, який я всім рекомендую — там можна написати скрипт улюбленою мовою та перетворити його текстовий вихід у віджет панелі меню macOS. Але ще залишається отримувати інформацію.

Для того в нас є два методи API. Перелічити запуски збірок для репозиторію — вертає всю потрібну інформацію. Їх навіть можна відфільтрувати за автором.

Але є нюанс. Для отримання інформації потрібний “класичний” токен авторизації з повними правами до всіх репозиторіїв — не хотілося б такий тримати відкритим. Або “fine-grained” токен — там є права тільки на читання GH Actions та тільки на конкретні репозиторії — що гарно. Але щоб бачити робочі репозиторії, він повинен належати робочій організації, на що треба ще отримати дозвіл від адміністратора. Що, як на мене, шкереберть — бо ж на класичний токен з повними привілеями дозволу не потрібно.

Друга проблема — що цей API обмежений одним репозиторієм, а в мене їх принаймні 6. Хоча 6 - це не 100, та можна всі їх опитувати по черзі. Є інший API - для сповіщень. Навіть доступний із класичним токеном з точно обмеженими правами. Але… сповіщення про збірки мають сміховинний мінімум інформації: репозиторій та текстовий опис — навіть не структурований. Навіть посилання на збірку в них немає, тому інтерфейс буде на рівні “щось десь впало, піди подивись.” (Краще, ніж нічого, звісно.)

Одним словом, наче й можна зібрати потрібний мені звіт (а саме — де тести впали, а де пройшли) - але виглядає ненадійно.

До речі, а не знаєте такого ПЗ, щоб в локальному браузері, з моєю сесією перевіряло зміст сторінки, а краще — дозволяло періодично виконувати скрипти? Бо можна було б ще з такого боку зайти.


09.01.2025

Син попросив приготувати: сніданки

В неділю син щось чи нафантазував, чи приколовся і каже: хочу на сніданок фотосинтез! Ну можна було посміятися та забути, а можна прийняти цей творчий кулінарний виклик. Не знав я тоді, що це тільки початок…

🥬 Понеділок: фотосинтез. Подолавши перше бажання дати лист салату та посвітити в очі ліхтариком, пішов з іншого боку. Ну власне “світлом” не наїсися… що може замінити світло? Як щодо жовтка яйця? Поклав на салат яйце пашот; яйце розрізаємо, жовток розтікається по салату, фотосинтез зараховано. 🌞

😶‍🌫️ Вівторок: аерозоль. Їстівних аерозолів теж не дуже багато. (Ну хіба оті цукерки-спреї… брр.) Але можна ще згадати, що пара — теж аерозоль. (От зараз ще подумав про дим — можна було б теж якийсь елемент включити.) В мене на пару асоціація конкретно з картоплею — як ото дихають картопляною парою від застуди. В середину тарілки йде піала з гарячою вареною картоплею з водичкою, а задля ефекту все це накрите кришкою. До речі, щоб пара дожила до столу, посуд повинен бути гарячим заздалегідь. Що взагалі хороша ідея, коли подаєш щось невелике — наприклад, яєшню. 🥔

🥗 Середа: емульсія. О, це вже значно легше. Бо більшість соусів — це емульсії. Можна було б обмежитися майонезом, але я заколотив прямо за столом айолі. Підсушив в аерогрилі шматочки хліба, порізав печену курячу грудку — салат “Цезар” готовий! (Не дуже автентичний, але де ви бачили автентичний “Цезар”?) 🧄

🥞 Четвер: піна. Тут теж складнощів немає. По-перше, всі хлібобулочні продукти — це тверда піна. По-друге, дві класичні кухонні піни — збиті білки та збиті вершки. Беремо перше, беремо друге… разом будуть панкейки зі збитими вершками зверху! Щоб було щось здорове, посередині кладу притушене яблуко зі спеціями. 🍎

🌯 Завтра беру перерву, бо досить. Взагалі в нас типова страва дитині на сніданок — це мʼясо з овочами в лаваші. І поживно, і здорово, і всередину можна багато всього покласти.


08.01.2025

Генерація JSON на Hugo

Мій бот скрипт для каналу — який бере зміст з файлів блогу на Hugo - був з самого початку збудований як повністю окреме рішення: він сам знаходить пости, сам їх парсить, сам їх надсилає в Telegram. З Hugo в нього спільні тільки домовленості про структуру тек та формат файлів.

Нещодавно мені спало на думку, що взагалі-то перші два кроки Hugo теж робить, та незрівнянно вправніше. Тому гарно було б якось включитися в Hugo та залишити на власний скрипт тільки публікацію. Зокрема, це розблокувало б інші напрямки публікації, бо наразі скрипт дуже завʼязаний на Telegram та розділити його мені поки не вдалося.

Механізму плагінів в Hugo немає, тож що я придумав: Hugo під час збірки буде генерувати мені JSON з всіма даними, що потрібні для публікації, а скрипту залишиться зчитати JSON та, так би мовити, “синхронізувати” його в канал.

…Механізму “згенерувати JSON” в Hugo теж немає. Зате ми можемо створити окрему сторінку, а в її шаблоні назбирати даних, викликати для них jsonify та надрукувати — от і буде JSON. На щастя, принаймні з кожної сторінки є необмежений доступ до даних сайту.

Величезний мінус — збирати дані доведеться всередині шаблону. Hugo використовує звичайні шаблони Go, але ніколи в Go мені не спадало на думку, скажімо, перетворювати в шаблоні масиви даних. Бо мова шаблонів незручна, не має підказок IDE, та ще й документація Hugo неповноцінна (в документації функції називаються, як в Go, а в шаблонах пишуться за псевдонімами, яких у переліках функцій та навіть в пошуці не видно).

Але, попри все це, мені вдалося! Найбільше часу витратив на спроби функціонального перетворення масивів — бо там наче є функція apply - аналог map - але виявилося, що дуже обмежена. А мова шаблонів — все ж імперативна. Тому, наприклад, щоб перетворити масив тегів-обʼєктів у масив назв, я оголошую новий масив та циклом складаю в нього назви. Коли це збагнеш, решта вже не так складно.

Так що чекайте в наступні тижні покращень по скрипту публікації, а може й публікації самого скрипту.


07.01.2025

Міграції в SQLite

🗑️ Стикнувся сьогодні з класичним багом в Ping: видалення тегу звалювалося через порушення FOREIGN KEY. Бо ключ я додати згадав, а ON DELETE CASCADE забув. Почав було виправляти на рівні застосунку (тобто видаляти всі відмітки, а потім сам тег), та й думаю… хіба це не робота для бази даних?

🪨 Але ось що виявилося: в SQLite команда ALTER TABLE здатна тільки на найпростіші зміни схеми: додати / видалити / перейменувати стовпчик, або перейменувати саму таблицю, і все. Що мене, звиклого до PostgreSQL, ввело в ступор: невже я застряг з поганою таблицею?

✂️ Знайшов відповідь на StackOverflow з цікавою порадою: оскільки схема таблиць в SQLite зберігається в спеціальній табличці, то достатньо зняти спеціальний запобігач PRAGMA writable_schema та можна просто замінити схему та додати до неї ON DELETE CASCADE. Ясно, що це виглядає максимально сумнівно.

💣 Сумнівно чи ні, але спробував; виявилося, що на iOS цей метод взагалі не працює, бо там підключення відбувається в особливому defensive mode, де абсолютно заборонені такі “сумнівні дії”. (Якщо в схемі змінити таким чином щось суттєве — типи чи порядок стовпчиків, наприклад — то база гепнеться.)

📖 Тоді почитав трохи документацію, та виявилося, що рекомендований підхід дуже простий: створити другу, “правильну” таблицю, наповнити її даними, а потім підмінити стару таблицю новою. Мінуси цього рішення: не відчуєш себе містером роботом. Плюси: все пройшло успішно, без втрати даних та цілісності.

❓ До речі, причина цього (яка теж є на тій же ж сторінці документації), прагматична. SQLite не зберігає схему в структурній формі, а тільки в текстовій (власне, зберігається команда CREATE TABLE.) Тому будь-які зміни в схемі повинні були б перекладатися назад в текст CREATE TABLE - ще й гарантовано без втрат змісту. Творці SQLite вирішили не ускладнювати цим проєкт та залишили зміни схеми в такому ручному, явному режимі.


06.01.2025

Робота без відволікань з Bunch

Взявся за те, щоб обмежити відволікання навколо робочого місця. Та, якщо на столі можна прибрати, двері зачинити, а телефон — приховати в шухлядку (до речі, всім раджу!), то з відкритими на компʼютері вікнами не так легко. Погодьтеся, зазвичай вікна не хочеться зачиняти, бо потім доведеться відкривати наново, налаштовувати стан… краще нехай всі будуть відкриті! Отримуємо цифровий аналог робочого стола, заваленого папірцями та матеріалами всіх активних проєктів.

Щоб спростити підготовку робочого місця на macOS є застосунок Bunch. Він почався як “ярлик до групи програм” (звідси назва), але з часом виріс в цілий інтерпретатор — заточений під керування програмами, вікнами, режимами ОС, та здатний на будь-які інші задачі.

Я Bunch використав, щоб закривати всі вікна та відкривати мені потрібні. Відповідно на кожну задачу є окремий Bunch-скрипт. Можливостей там безліч. Мій скрипт виглядає приблизно так:

# закриваю все, окрім "системних" застосунків
(quit all except Drafts, Obsidian, Music)
# VSCode в AppleScript не вміє, тому відкриваю воркспейс командою терміналу
$ code ~/projects/ping/ping.code-workspace
# запускаю XCode з файлом проєкту
XCode
- ~/projects/ping/ping.xcodeproj
# ...запускаю проєкт комбінацією Cmd+R
- {@r}
# роблю переднім вікном VSCode
Visual Studio Code^

Це тільки разове налаштування стану, воно не забороняє потім просто відкривати будь-які вікна тощо. Я ставлюся до нього саме так, як до прибирання фізичного столу: підготувався до роботи, а далі роблю те, що зручно.

Закривати всі вікна спочатку страшно, а потім тренує залишати роботу в завершеному стані, зокрема в браузері.


05.01.2025

JSON-бази для простих застосунків

Я якось писав, що використовую JSON для зберігання даних мого застосунку для GTD. Застосунок досі живий та я ним користуюся постійно, тож JSON виріс та займає цілих… 120 кб!

А от на тому тижні натрапив на пост, де згадувалась “база” pickleDB для Python (та ще декілька альтернатив.) Це фактично той самий єдиний JSON, але з накладеною зверху абстракцією “документів”, “списків”, та “словників” - певно, щоб код хоч трохи наближався до такого, що буде працювати зі звичайною базою. (До речі, подивився навколо, та здається, що на Python такі мікробази взагалі популярні — а на Ruby нічого схожого не бачив. Цікаво)

Хотілося додати про досвід своєї “JSON-бази” - зокрема, де я бачу її межі. Головне те, що JSON можна читати та писати тільки цілим документом. Це наче і не проблема, бо є бібліотеки, які здатні читати та писати гігабайти JSON в секунду. Але ж нам також доведеться писати його на диск, а це вже повільніше. Плюс не забути увімкнути атомарний запис, інакше ризикуємо через форс-мажор записати половину файлу та втратити все. Плюс я б не хотів робити синхронізацію доступу через файл, якщо база потрібна більш ніж в одному процесі.

Також доведеться читати та тримати в памʼяті весь зміст разом — це відразу накладає ціну, особливо в мовах, де обʼєкти в пам’яті займатимуть більше місця, ніж сам JSON. Я з таким стикнувся навіть в Go. Але, це стане помітно на сотнях мегабайтів даних.

Гадаю, все залежить від того, як часто вам потрібно читати чи писати в базу. Якщо читання відбувається під час запуску застосунку, а запис — на дії користувача, то JSON вам (як і мені) вистачатиме надовго.


04.01.2025

Моя спроба зробити Undo/Redo

Був в мене проєкт застосунку для самоменеджменту… який не досяг успіху через відсутність бачення. Втім, рушій я для нього нагородив досить потужний. Застосунок був на React Native, а стан зберігався у базі PouchDB, яка синхронізувалася з сервером CouchDB. Для мене, як я вже писав, CouchDB досі залишається потужною, але нішевою технологією без зрозумілого застосування — приблизно як Clojure.

Отже, механізм Undo/Redo, тобто “скасувати/повторити”. Зазвичай його складність полягає в неоднорідності операцій зі станом. Так, в багатьох (чи усіх) застосунках текстові операції є скасованими, але спробуй змінити блоки, що містять той текст — наприклад, задачі — та можливість скасування втрачається.

CouchDB тут має корисну властивість. Документи в ній мають ревізії, та старі ревізії зберігаються поруч з новими. Це потрібно для реплікації. Є схеми, де реплікуються зміни, а ось в CouchDB реплікуються цілі ревізії. Таким чином, досить довго можна прочитати стару версію документа. Навіть видалення відбувається через нову ревізію з маркером _deleted. Тому: всі дії в моєму проєкті проходили через єдину операцію put, а також — я завжди мав доступ до старих ревізій всіх документів.

Але очевидно, для користувача інтерфейс повинен бути вище за скасування “документ за документом, ревізія за ревізією”. Тому всі функції-дії, що потребували скасування, загорталися в функцію withUndo та отримували всередині особливу реалізацію інтерфейсу DBEffects, яку потім передавали усім операціям з базою. А інтерфейс DBEffects складався з єдиної функції put… з Undo вона не тільки писала в базу, а й складала перелік документів та їх ревізій до об’єкта, створеного в функції withUndo. (Звісно, через put були реалізовані звичайні create, update, delete і так далі.)

Потім, щоб скасувати останню дію, я брав крайній обʼєкт з переліку дій, та вертав назад попередній стан порушених документів. Ось так просто. (Ревізії утворювались нові, до речі, бо як і в Git, старі дані не можна змінити.) Та це дійсно працювало з будь-якими змінами, від примітивних до складних.


03.01.2025

Що робить інтерфейси "дорогими"?

✨ Короткий списочок того, що я б хотів бачити у застосунках, над якими працюю — але на це ніколи немає часу. (Найближче була Сінтра, оскільки пан Олександр Зайцев максимально прискіпливо ставиться до деталей.)

🥳 PS: стохастичний таймтрекер вже доступний для бета-тестування, доєднатися можна за посиланням.