window.innerWidth can briefly report the desktop viewport width when
the page is restored from a long Android background session, causing
openChat() to skip adding chat-open even though chatIsOpen is true.
Replace all window.innerWidth checks with mobileQuery.matches so the
JS breakpoint always agrees with CSS. Also swap the resize listener
for mobileQuery.addEventListener('change') which only fires when the
breakpoint actually crosses, and add a visibilitychange handler to
re-apply chat-open when the app returns to the foreground.
- Persist display names in per-channel JSON cache files (Name +
DisplayName fields on cachedChannel). GetAllTitles reads all ch_*.json
files; PutTitle updates a channel file in-place without losing messages.
- Replace blocking FetchTitles calls in fetchMeta and refreshChannel with
an instant disk read (GetAllTitles) applied before the SSE broadcast,
so channels appear with cached titles immediately on every load.
- ensureTitlesFetched runs in a single background goroutine (titlesMu +
titlesLoading guard prevents duplicates). On success it persists titles
and pushes an SSE update. On error or empty response it backs off for
5 minutes so an old server does not cause endless retries.
- Block 0 of TitlesChannel now carries a uint16 total-block-count prefix
(added in rebuildTitlesBlocks). FetchTitles reads the count from block 0
and fetches all remaining blocks in parallel instead of sequentially.
- FetchTitles timeout raised from 10 s to 1 minute.
- Channel list: always show @handle as subtitle below display name for Telegram channels
- Chat header: show @handle as subtitle below channel title
- Mobile (<768px): hide Search/Export buttons; show them in a ⋮ kebab menu (rightmost button)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add dedicated TitlesChannel (0xFFF9) following the VersionChannel pattern
- Server encodes name→title map via EncodeTitlesData/DecodeTitlesData
- Metadata wire format unchanged for backward compatibility with old clients
- All three fetchers (public Telegram, MTProto, X/Nitter) extract and store display names
- Client fetches TitlesChannel with a 10s deadline; falls back to channel handles gracefully on old servers
- Old clients are unaffected — they never query 0xFFF9
- Add DisplayName field to ChannelInfo in the wire protocol
- Add displayNames map to Feed; SetChannelDisplayName no longer mutates
f.channels, keeping handles stable for cache keys and management
- Public fetcher: extract title via extractChannelTitle, pass to feed
- Telegram fetcher: capture ch.Title / user.FirstName from MTProto API
- Frontend: render DisplayName as sidebar label, keep Name as identifier
Previously linkify received pre-escaped text, so & in URLs became &
causing the regex to truncate URLs at query-string boundaries. Now
linkify escapes HTML internally so URLs are matched against raw text.
Also adds [label](url) markdown link support.
- Implemented /api/cache/clear endpoint to delete all files in the cache directory.
- Added tests for cache clear functionality, including cases for empty cache and method not allowed.
- Introduced /api/rescan endpoint to trigger a manual rescan of the configured channels.
- Added tests for rescan functionality, covering cases for not configured, method not allowed, and successful rescans.
- Enhanced server initialization to bootstrap configuration from active profile if config.json is missing.
- Refactor fetcher tests to improve coverage and add new tests for resolver scoring and query handling.
- Update ResolverChecker to retry every minute until a healthy resolver is found and adjust timeout settings.
- Introduce scatter parameter in the configuration to allow concurrent DNS requests, with UI adjustments for user input.
- Modify metadata fetching logic to utilize cached data efficiently and ensure fresh data is fetched when necessary.
- Implement server-side handling for fetch progress, including countdowns and UI updates during long fetch operations.