diff --git a/FEEDBACK.md b/FEEDBACK.md index 9580c7a..c73e286 100644 --- a/FEEDBACK.md +++ b/FEEDBACK.md @@ -699,3 +699,15 @@ - **Status:** done - **Completed:** 2026-02-13 — commit `84512b1` - **Description:** Avatar image shows as broken for some deployments. Bardak's instance (deployed by Pelouse) shows a broken image. Works on Nicolas's instance. Likely the avatar URL configured by Pelouse is invalid or blocked. PinchChat should handle broken avatar images gracefully (fallback to initials or default icon). + +## Item #67 +- **Date:** 2026-02-13 +- **Priority:** medium +- **Status:** pending +- **Description:** Add an update indicator next to the version number in the UI. When a newer Docker image/release is available on GitHub (compare current version vs latest GitHub release tag), show a visual indicator (badge, dot, or link) so users know they can update. + +## Item #68 +- **Date:** 2026-02-13 +- **Priority:** high +- **Status:** pending +- **Description:** Cursor desync in textarea STILL present after v1.39.2 fix. The cursor position still gets ahead of where characters actually appear. The previous fix (removing padding/bold from backdrop tokens) was insufficient. Need deeper investigation — likely the HighlightedTextarea backdrop and textarea have mismatched rendering (font metrics, line-height, word-wrap differences, or span wrapping in the backdrop creating different text flow). Consider disabling highlight entirely as test, or ensuring backdrop uses identical character-level rendering with zero extra styling that could affect text width/flow. diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index efaa924..2eb4e38 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,10 +1,33 @@ import { useState, useMemo, useRef, useEffect, useCallback } from 'react'; -import { X, Sparkles, Search, Pin, Trash2, Columns2, Clock, Bot, MessageSquare, Globe, Zap } from 'lucide-react'; +import { X, Sparkles, Search, Pin, Trash2, Columns2, Clock, Bot, MessageSquare, Globe, Zap, ArrowUpCircle } from 'lucide-react'; import type { Session } from '../types'; import { useT } from '../hooks/useLocale'; import { SessionIcon } from './SessionIcon'; import { sessionDisplayName } from '../lib/sessionName'; import { relativeTime } from '../lib/relativeTime'; +import { useUpdateCheck } from '../hooks/useUpdateCheck'; + +function VersionBadge() { + const update = useUpdateCheck(__APP_VERSION__); + if (update.available) { + return ( + + v{__APP_VERSION__} + + v{update.latestVersion} + + ); + } + return ( + v{__APP_VERSION__} + ); +} const PINNED_KEY = 'pinchchat-pinned-sessions'; const WIDTH_KEY = 'pinchchat-sidebar-width'; @@ -490,7 +513,7 @@ export function Sidebar({ sessions, activeSession, onSwitch, onDelete, onSplit, - v{__APP_VERSION__} + {/* Resize drag handle */}
({ available: false, latestVersion: null, releaseUrl: null }); + + useEffect(() => { + let timeout: ReturnType; + + async function check() { + try { + // Check cache first + const cached = localStorage.getItem(CACHE_KEY); + if (cached) { + const { version, url, ts } = JSON.parse(cached); + if (Date.now() - ts < CHECK_INTERVAL) { + if (version && isNewer(version, currentVersion)) { + setInfo({ available: true, latestVersion: version, releaseUrl: url }); + } + timeout = setTimeout(check, CHECK_INTERVAL - (Date.now() - ts)); + return; + } + } + + const res = await fetch(GITHUB_API, { headers: { Accept: 'application/vnd.github.v3+json' } }); + if (!res.ok) return; + const data = await res.json(); + const tag: string = data.tag_name?.replace(/^v/, '') || ''; + const url: string = data.html_url || ''; + + localStorage.setItem(CACHE_KEY, JSON.stringify({ version: tag, url, ts: Date.now() })); + + if (tag && isNewer(tag, currentVersion)) { + setInfo({ available: true, latestVersion: tag, releaseUrl: url }); + } + } catch { /* silent */ } + timeout = setTimeout(check, CHECK_INTERVAL); + } + + check(); + return () => clearTimeout(timeout); + }, [currentVersion]); + + return info; +} + +/** True if remote is newer than local (semver compare) */ +function isNewer(remote: string, local: string): boolean { + const r = remote.split('.').map(Number); + const l = local.split('.').map(Number); + for (let i = 0; i < 3; i++) { + if ((r[i] || 0) > (l[i] || 0)) return true; + if ((r[i] || 0) < (l[i] || 0)) return false; + } + return false; +} diff --git a/src/index.css b/src/index.css index 5c711c0..a947ca8 100644 --- a/src/index.css +++ b/src/index.css @@ -212,11 +212,12 @@ html, body { .ht-backdrop, .ht-textarea { - /* Must share identical text layout */ + /* Must share EXACTLY identical text layout — any difference causes cursor desync */ font-family: inherit; - font-size: 0.875rem; /* text-sm */ - line-height: 1.25rem; - padding: 0.75rem 1rem; /* py-3 px-4 */ + font-size: inherit; + line-height: inherit; + letter-spacing: inherit; + word-spacing: inherit; white-space: pre-wrap; word-break: break-word; overflow-wrap: break-word; @@ -224,6 +225,7 @@ html, body { overflow-y: auto; margin: 0; box-sizing: border-box; + /* padding, border, border-radius all come from the shared className */ } .ht-backdrop { @@ -231,9 +233,7 @@ html, body { inset: 0; pointer-events: none; color: var(--pc-text-primary); - border: 1px solid transparent; /* match textarea border width to keep text aligned */ - border-radius: 1rem; /* rounded-2xl */ - max-height: 200px; + background: transparent; } .ht-textarea { @@ -246,38 +246,31 @@ html, body { z-index: 1; } -/* Token colors */ +/* Token colors — ONLY color allowed, nothing that changes text geometry */ .ht-code-block { color: #67e8f9; /* cyan-300 */ - background: var(--pc-accent-glow); - border-radius: 4px; } .ht-inline-code { color: #67e8f9; - background: var(--pc-accent-glow); - border-radius: 3px; - /* No padding — must match textarea text layout exactly to avoid cursor desync */ } .ht-bold { color: var(--pc-text-primary); - /* No font-weight change — bold text is wider and causes cursor desync */ } .ht-italic { color: var(--pc-text-secondary); - font-style: italic; + /* No font-style: italic — italic glyphs have different widths, causes cursor desync */ } .ht-heading { color: #a78bfa; /* violet-400 */ - font-weight: 600; + /* No font-weight — bolder glyphs are wider, causes cursor desync */ } .ht-link { color: #67e8f9; - text-decoration: underline; } /* Accessibility: respect reduced-motion preferences */