Building CodeTune v1.2.0: Islamic Date Tracking & Audio Sync in a VS Code Extension

від

у

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: Пояснення ісламського відстеження користувачам Однією з проблем було те, що користувачі могли подумати, що трекер зламаний, якщо їх щоденні цілі не скидаються опівночі. Рішення — невелика підказка поряд з заголовком панелі приладу:

ℹ️

🕌 Smart Islamic Tracking
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