
CodeTune v1.2.0: Відстеження ісламської дати та синхронізація аудіо в розширенні VS Code
https://ift.tt/J4Iw8Tu
Як я реалізував відстеження дня за Фадж (Fajr), виправив синхронізацію аудіо між webview та хостом розширення і модульно розробив ісламське продуктивне розширення для VS Code
Я розробляю CodeTune — розширення для VS Code, яке допомагає мусульманським розробникам залишатися духовно пов’язаними під час кодування. Воно програва Quran- recitations, показує ісламські нагадування, відстежує Dhikr та обчислює час молитви — все це не виходячи з редактора.
Version 1.2.0 був великою релізом: 14 виправлень помилок, повна модульність JavaScript-слою та, що найважливіше — налаштована система Islamic day tracking, яка дотримується Фаджа як початку дня, а не опівночі.
Ось повний технічний розбір того, що змінилося і чому.
🕌 Основна проблема: межі ісламського дня
В ісламі новий день починається у Магріб (заходу сонця) для місячного календаря, а духовно багато вчених вважають Фадж (на світанку) межою для щоденних поклонінь, таких як читання Корану та Dhikr.
Це означає, що якщо користувач виконує Dhikr о 2:00 ночі, то він все ще перебуває в «вчорашньому» духовному дні — Фадж ще не настав.
Немодельна реалізація з опорною межою дня на опівночі призводила до:
– некоректного скидання послідовності вночі;
– показу “День 0” під час фактичного поклоніння;
– порушення логіки подій п’ятниці (Сура Аль-Кахф має починатися з четверга після Магріба).
Рішення: getHabitTrackingDate()
private getHabitTrackingDate(date: Date = new Date()): string {
const now = new Date(date);
// За замовчуванням 4:00, якщо API Фаджа ще не відповіло
let fajrHour = 4;
let fajrMinute = 0;
if (this.currentFajrTime) {
fajrHour = this.currentFajrTime.getHours();
fajrMinute = this.currentFajrTime.getMinutes();
}
const fajrTimeToday = new Date(now);
fajrTimeToday.setHours(fajrHour, fajrMinute, 0, 0);
// Якщо до Фаджа — то вчорашній ісламський день
if (now < fajrTimeToday) {
now.setDate(now.getDate() - 1);
}
return this.formatDate(now);
}
Ця єдина функція тепер є єдиним джерелом правди для всіх дат у трекері. Більше немає розкиданих getTodayString() по кодовій базі.
Тривимірно: часові арифметики з урахуванням часових пояcів
Одна з тонких помилок: обчислення «вчора» з використанням new Date(dateString) може зміщувати дати через зсуви часових поясів. Виправлення — завжди використовувати опівдні (12:00:00) як опору часу:
private getPreviousDateString(baseDateStr: string, daysBack: number): string {
const [y, m, d] = baseDateStr.split('-').map(Number);
// опорний час на опівдні
const date = new Date(y, m - 1, d, 12, 0, 0);
date.setDate(date.getDate() - daysBack);
return this.formatDate(date);
}
Як Фадж тече через систему
Фадж-час надходить з двох джерел і має дійти до SpiritualTracker:
Source 1 — IP-геолокація при запуску (ActivityBarViewProvider.ts):
case 'requestPrayerTimes':
const times = calculatePrayerTimes(data.lat, data.lon);
this.sendMessageToWebview({ type: 'receivePrayerTimes', payload: times });
// Оновлення SpiritualTracker реальним часом Фадж
const tracker = (global as any).spiritualTracker as SpiritualTracker;
if (tracker && times.fajr) {
tracker.updateFajrTime(new Date(times.fajr));
}
break;
Source 2 — IslamicRemindersManager при ініціалізації (IslamicRemindersManager.ts):
private syncFajrTimeWithTracker() {
if (!this.spiritualTracker) return;
try {
const prayerTimes = IslamicCalendar.calculatePrayerTimes();
if (prayerTimes?.fajr) {
this.spiritualTracker.updateFajrTime(prayerTimes.fajr);
}
} catch (error) {
logger.warn('Could not sync Fajr time with Tracker:', error);
}
}
Результат: як тільки розширення завантажується, SpiritualTracker отримує час Фаджа з локального обчислення, а коли визначиться GPS/IP-локація — оновлюється до точного значення.
🎵 Виправлення синхронізації аудіо → трекер помилки
Ця помилка була найпомітнішою у релізі. Показник прогресу духовності завжди відображав 0/15 хвилин для прослуховування Корану, навіть після годин відтворення аудіо.
Root Cause
Дані потоку зламані на кроці 2:
- ✅ Аудіо відтворюється → зберігає час у localStorage (webview)
- ❌ Відсутні дані localStorage ніколи не надсилаються хосту розширення
- ❌ SpiritualTracker залишався на 0 хвилинах
- ❌ Панель приладу зчитувала з SpiritualTracker → показувала 0
Webview та хост розширення — це окремі процеси. localStorage у webview не видно для коду розширення на TypeScript.
Fix: патерн акумулятора (Time Buffer)
Наївне рішення: константа elapsedMinutes = Math.round(elapsedTime / 60000); якщо > 0 — logQuranTime з minutes: elapsedMinutes. Проблема: користувач, який слухає 3×45 секунд, отримує 0 хвилин logs.
Кращий підхід — акумулювати мілісекунди доки не згенерується повна хвилина:
// У конструкторі
this.unloggedMs = 0;
stopTimeTracking() {
if (!this.playStartTime) return;
const now = Date.now();
const elapsedMs = now – this.playStartTime;
this.currentSessionTime += elapsedMs;
// Додати до буфера часу
this.unloggedMs += elapsedMs;
// локальне зберігання статистики прослуханого
const today = new Date().toISOString().split(‘T’)[0];
if (!this.listeningStats.dailyTimeStats[today]) {
this.listeningStats.dailyTimeStats[today] = 0;
}
this.listeningStats.totalTimePlayed += elapsedMs;
this.listeningStats.dailyTimeStats[today] += elapsedMs;
this.listeningStats.lastUpdated = Date.now();
this.saveListeningStats();
// Лише повні хвилини надсилаємо до SpiritualTracker
const minutesToLog = Math.floor(this.unloggedMs / 60000);
if (minutesToLog > 0) {
this.postMessage(‘logQuranTime’, { minutes: minutesToLog });
// Зберегти залишок для наступної сесії
this.unloggedMs -= minutesToLog * 60000;
}
this.playStartTime = null;
this.currentSessionTime = 0;
}
Чому так правильно:
Сценарій | Розрахунок (Broken) | Розрахунок (Fixed)
– 3×45-секундні сесії | 0 хвилин логовано | приблизно 2.25 хвилин логовано
– 1×90-секундна сесія | 2 хвилини логовано (некоректно) | 1 хвилина логована, 30 с збережено
– 1×5-хвилинна сесія | 5 хвилин | 5 хвилин
Патерн акумулятора забезпечує, що жоден прослуханий час не втрачається і не додаються фантомні хвилини.
Расклад Стріки (Streak Calculation)
Логіка стрика також потребувала врахування границі Фаджа. Користувач, який не зробив Dhikr сьогодні (наприклад, зараз 10 ранку), не повинен втрачати свою серію — в них ще є час до наступного Фаджа.
private calculateCurrentStreak(activeDates: string[]): number {
if (activeDates.length === 0) return 0;
const activeSet = new Set(activeDates);
const todayStr = this.getHabitTrackingDate();
const yesterdayStr = this.getPreviousDateString(todayStr, 1);
// Стрік зберігається якщо є активність сьогодні або вчора
if (!activeSet.has(todayStr) && !activeSet.has(yesterdayStr)) {
return 0;
}
let streak = 0;
let currentCheckStr = activeSet.has(todayStr) ? todayStr : yesterdayStr;
for (let i = 0; i < 365; i++) { if (activeSet.has(currentCheckStr)) { streak++; currentCheckStr = this.getPreviousDateString(currentCheckStr, 1); } else { break; } } return streak; } UX: Пояснення ісламського відстеження користувачам Однією з проблем було те, що користувачі могли подумати, що трекер зламаний, якщо їх щоденні цілі не скидаються опівночі. Рішення — невелика підказка поряд з заголовком панелі приладу:
Daily goals reset at Fajr (not midnight).
Friday events begin after Maghrib the evening before.
.info-tooltip-content {
visibility: hidden;
opacity: 0;
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%) translateY(10px);
width: 220px;
background: var(–glass-bg, rgba(30, 30, 30, 0.95));
backdrop-filter: blur(10px);
border: 1px solid var(–glass-border, rgba(255, 255, 255, 0.1));
border-radius: 8px;
padding: 12px;
font-size: 12px;
transition: all 0.3s ease;
pointer-events: none;
}
.info-tooltip-container:hover .info-tooltip-content {
visibility: visible;
opacity: 1;
transform: translateX(-50%) translateY(0);
}
Маленька деталь, великий вплив — користувачі тепер розуміють функцію замість того, щоб обмежувати її як помилку.
Інші значні виправлення помилок
– Dhikr лічильники не оновлювали панель приладу — лічильник оновлював локальний стан, але ніколи не повідомляв хосту розширення:
Before: incrementSalawatCounter() оновлював тільки локальний UI
After: також синхронізується з SpiritualTracker через window.vscode.postMessage({ type: ‘incrementDhikr’, count: 1 });
– Adhkar з дефісними назвами не вирішувався — rabbi-ghifir розпізнавався як єдиний рядок, замість конвертації на camelCase: rabbiGhifir
function incrementAdhkarCounter(adhkarName) {
const camelCaseName = adhkarName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
// Тепер правильно вирішує rabbi-ghifir → rabbiGhifir
}
– Відсутні конфігураційні властивості — VS Code показував “Unable to write to User Settings”, бо fridaySurahLastReset та fridaySurahState ніколи не були зареєстровані в package.json:
{
“contributes”: {
“configuration”: {
“properties”: {
“codeTune.fridaySurahLastReset”: {
“type”: “string”,
“default”: “”
},
“codeTune.fridaySurahState”: {
“type”: “string”,
“default”: “disabled”
}
}
}
}
}
– Webview не може завантажити new_features.txt — отримував 403 Forbidden через відсутність src/media у localResourceRoots:
localResourceRoots: [
vscode.Uri.file(path.join(context.extensionPath, ‘src’, ‘media’)),
vscode.Uri.file(path.join(context.extensionPath, ‘images’))
]
Файли, змінені у версії v1.2.0
– src/utils/SpiritualTracker.ts — ісламська дата, відстеження за Фадж
– src/logic/activityBarView.ts — синхронізація часу молитви, оновлення Фаджа за локацією
– src/logic/islamicReminders.ts — інтеграція зі SpiritualTracker
– src/ui/components/audioPlayer.js — виправлення акумулятора синхронізації аудіо
– src/ui/components/counter.js — синхронізація Dhikr з SpiritualTracker
– src/ui/components/trackerDashboard.js — підказка (info tooltip)
– src/ui/components/trackerDashboard.css — обгортка картки, виправлення модальних вікон
– package.json — відсутні конфігураційні властивості
– Видалено: src/ui/components/statistics.js (дубль дашборду)
Що далі
– v1.3.0 зосередиться на Settings & Localization — виправлення всіх панелей налаштувань, локалізація кожного рядка на 5 мов (EN, AR, ES, FR, RU) та уніфікація UI налаштувань по всіх компонентах.
– Якщо ви розробляєте VS Code-розширення з webview, ключовий урок цієї релізу: webview та хост розширення — це окремі процеси. Будь-яке стан, який потрібно зберегти або передати, має явно переходити через місто повідомлень. localStorage — лише sandbox webview.
Розширення є з відкритим кодом — будь-який зворотний зв’язок та внески вітаються.
Ем بارك الله فيكم 🤲
Посилання
– 📦 VS Code Marketplace — Install CodeTune: https://ift.tt/TQYuz4D
– 🟣 Open VSX Registry — Install CodeTune: https://ift.tt/BdXPn0i
– 🐙 GitHub — kareem2099/codetune: https://ift.tt/34f1c0N
HI-FI News
via DEV Community: javascript https://ift.tt/Lzogay8
March 28, 2026 at 10:01AM
March 28, 2026 at 10:01AM

Залишити відповідь
Щоб відправити коментар вам необхідно авторизуватись.