feat: режим индикаторов без цветовой кодировки + переустройство /setup

Новая настройка app_settings.indicator_mode (color | accessible), переключается
на /setup. В режиме accessible сигналы, которые раньше держались только на цвете,
переходят на форму/текст:
- качество цены (0-9) — 5-ступенчатый signal-meter вместо градиентной точки/числа;
  единый модуль web/static/price-quality.js, ветвление по window.QF_INDICATOR_MODE;
- вкладка «Ценообразование»: ведущая колонка-meter вместо заливки строк,
  пометка W вместо амбер-подсветки world-цен, ⚠ вместо красного «загрязнённого» итога.
- GET/PUT /api/settings/ui (без рестарта), регистрируется в обоих наборах роутов.

/setup переустроен: две колонки одной высоты, кнопка «Вернуться в приложение»,
исправлен <title>.

Документация: bible-local 02/03/04 + decisions/2026-08-31-accessible-indicator-mode.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbRvQgPZpM4SJTaX3iLrXk
This commit is contained in:
Mikhail Chusavitin
2026-08-31 18:08:38 +03:00
co-authored by Claude Sonnet 5
parent d97ac447ca
commit 681ec15e2b
15 changed files with 453 additions and 52 deletions
+47
View File
@@ -0,0 +1,47 @@
package handlers
import (
"net/http"
"git.mchus.pro/mchus/quoteforge/internal/localdb"
"github.com/gin-gonic/gin"
)
// SettingsHandler serves per-client UI display preferences stored in app_settings.
type SettingsHandler struct {
localDB *localdb.LocalDB
}
func NewSettingsHandler(localDB *localdb.LocalDB) *SettingsHandler {
return &SettingsHandler{localDB: localDB}
}
type uiSettingsResponse struct {
IndicatorMode string `json:"indicator_mode"`
}
// GetUI returns the current UI display preferences.
func (h *SettingsHandler) GetUI(c *gin.Context) {
c.JSON(http.StatusOK, uiSettingsResponse{
IndicatorMode: h.localDB.GetIndicatorMode(),
})
}
// PutUI updates UI display preferences. Currently only indicator_mode.
func (h *SettingsHandler) PutUI(c *gin.Context) {
req, ok := BindJSON[struct {
IndicatorMode string `json:"indicator_mode"`
}](c)
if !ok {
return
}
if err := h.localDB.SetIndicatorMode(req.IndicatorMode); err != nil {
RespondError(c, http.StatusUnprocessableEntity, "invalid indicator_mode", err)
return
}
c.JSON(http.StatusOK, uiSettingsResponse{
IndicatorMode: h.localDB.GetIndicatorMode(),
})
}
+4 -2
View File
@@ -10,6 +10,7 @@ import (
"time"
qfassets "git.mchus.pro/mchus/quoteforge"
"git.mchus.pro/mchus/quoteforge/internal/appmeta"
"git.mchus.pro/mchus/quoteforge/internal/db"
"git.mchus.pro/mchus/quoteforge/internal/localdb"
"github.com/gin-gonic/gin"
@@ -56,7 +57,9 @@ func (h *SetupHandler) ShowSetup(c *gin.Context) {
settings, _ := h.localDB.GetSettings()
data := gin.H{
"Settings": settings,
"Settings": settings,
"IndicatorMode": h.localDB.GetIndicatorMode(),
"AppVersion": appmeta.Version(),
}
tmpl := h.templates["setup.html"]
@@ -207,4 +210,3 @@ func buildMySQLDSN(host string, port int, database, user, password string, timeo
}
return cfg.FormatDSN()
}
+4
View File
@@ -114,6 +114,10 @@ func NewWebHandler(_ string, localDB *localdb.LocalDB) (*WebHandler, error) {
func (h *WebHandler) render(c *gin.Context, name string, data gin.H) {
data["AppVersion"] = appmeta.Version()
data["IndicatorMode"] = localdb.IndicatorModeColor
if h.localDB != nil {
data["IndicatorMode"] = h.localDB.GetIndicatorMode()
}
c.Header("Content-Type", "text/html; charset=utf-8")
tmpl, ok := h.templates[name]
if !ok {