--- description: Rules for working with the Telegram Mini App frontend (webapp/) globs: - webapp/** --- # Frontend — Telegram Mini App ## Module structure All source lives in `webapp/js/`. Each module has a single responsibility: | Module | Responsibility | |--------|---------------| | `main.js` | Entry point: theme init, auth gate, `loadMonth`, navigation, swipe gestures, sticky scroll | | `dom.js` | Lazy DOM getters (`getCalendarEl()`, `getDutyListEl()`, etc.) and shared mutable `state` | | `i18n.js` | `MESSAGES` dictionary (en/ru), `getLang()`, `t()`, `monthName()`, `weekdayLabels()` | | `auth.js` | Telegram `initData` extraction (`getInitData`), `isLocalhost`, hash/query fallback | | `theme.js` | Theme detection (`getTheme`), CSS variable injection (`applyThemeParamsToCss`), `initTheme` | | `api.js` | `apiGet`, `fetchDuties`, `fetchCalendarEvents`; timeout, auth header, `Accept-Language` | | `calendar.js` | `dutiesByDate`, `calendarEventsByDate`, `renderCalendar` (6-week grid with indicators) | | `dutyList.js` | `dutyTimelineCardHtml`, `dutyItemHtml`, `renderDutyList` (monthly duty timeline cards) | | `dayDetail.js` | Day detail panel — popover (desktop) or bottom sheet (mobile), `buildDayDetailContent` | | `hints.js` | Tooltip positioning, duty marker hint content, info-button tooltips | | `dateUtils.js` | Date helpers: `localDateString`, `dutyOverlapsLocalDay/Range`, `getMonday`, `formatHHMM`, etc. | | `utils.js` | `escapeHtml` utility | | `constants.js` | `FETCH_TIMEOUT_MS`, `RETRY_DELAY_MS`, `RETRY_AFTER_ACCESS_DENIED_MS` | ## State management A single mutable `state` object is exported from `dom.js`: ```js export const state = { current: new Date(), // currently displayed month lastDutiesForList: [], // duties array for the duty list todayRefreshInterval: null, // interval handle lang: "en" // 'ru' | 'en' }; ``` - **No store / pub-sub / reactivity.** `main.js` mutates `state`, then calls render functions (`renderCalendar`, `renderDutyList`) imperatively. - Other modules read `state` but should not mutate it directly. ## HTML rendering - Rendering functions build HTML strings (concatenation + `escapeHtml`) or use `createElement` + `setAttribute`. - Always escape user-controlled text with `escapeHtml()` before inserting via `innerHTML`. - `setAttribute()` handles attribute quoting automatically — do not manually escape quotes for data attributes. ## i18n ```js t(lang, key, params?) // → translated string ``` - `lang` is `'ru'` or `'en'`, stored in `state.lang`. - `getLang()` reads **backend config** only: `window.__DT_LANG` (set by `/app/config.js` from `DEFAULT_LANGUAGE`). If missing or invalid, falls back to `"en"`. No Telegram or navigator language detection. - Params use named placeholders: `t(lang, "duty.until", { time: "14:00" })`. - Fallback chain: `MESSAGES[lang][key]` → `MESSAGES.en[key]` → raw key string. - All user-visible text must go through `t()` — never hardcode Russian strings in JS. ## Telegram WebApp SDK - Loaded via `