Files
QuoteForge/bible-local/decisions/2026-08-31-accessible-indicator-mode.md
Mikhail ChusavitinandClaude Sonnet 5 681ec15e2b 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
2026-08-31 18:08:38 +03:00

3.7 KiB
Raw Permalink Blame History

Decision: indicator_mode — a colour-optional rendering mode for UI signals

Date: 2026-08-31 Status: active

Context

Several UI signals were encoded by hue alone and unreadable with a colour-vision deficiency:

  • price_quality (09) as a red→yellow→green gradient — a dot in the configurator search dropdown and inline before lot_name, a coloured number in the pricelist "Качество" column, a full-row background tint on the "Ценообразование" tab;
  • the amber world-fallback price cell tint (2026-07-10-world-pricelist-fallback.md);
  • the red "contaminated total" in the pricing footer (2026-07-24-pricing-total-world-share.md).

The bible has no dedicated accessibility contract, but table-management §Icon Semantics, controls-selection §Buttons, go-code-style §Business Logic Placement and build-version-display say the same thing: meaning is never carried by one ambiguous channel — back it with shape / number / explicit title+aria-label, and keep the indicator visually subordinate.

Decision

One per-client preference, app_settings.indicator_mode = color (default) | accessible, set on /setup ("Настройки") via GET/PUT /api/settings/ui (internal/handlers/settings.go) — no restart. Deliberately named for the concern (how indicators are rendered), not for the current widget, so future colour-only signals attach to the same switch.

internal/handlers/web.go render() and internal/handlers/setup.go inject the mode for every page; base.html publishes it as window.QF_INDICATOR_MODE. web/static/price-quality.js is still the single source of the price-quality scale and branches on the mode internally, so its call sites do not change.

In accessible mode:

  • price_quality renders as a 5-step signal-strength meter (priceQualityLevel = min(4, floor(score/2))0-1 / 2-3 / 4-5 / 6-7 / 8-9), five bars in one neutral hue, each instance carrying title/aria-label = Качество цены: N/9. qualityDotHtml and qualityBadgeHtml return the meter;
  • qualityRowStyle returns '' — the pricing tab instead shows the meter in a narrow leading column (conditional <th> / colspan in index.html, per-sub-row <td>);
  • the world-fallback amber class is dropped for a W superscript text marker on the price (text, not a colour class — applyCustomPrice's colour-stripping regex is a non-issue);
  • _setPricingTotal drops text-red-600 for a leading glyph, on the same trigger as the red (worldShare > 0). The hover popup — including the "prices for N of M positions" coverage line — is unchanged in both modes; per 2026-07-24 there is still no colour-coded coverage-only signal, and accessible mode does not add one.

color mode is byte-for-byte the previous behaviour.

Consequences

  • Default unchanged; nothing moves until a user opts in on /setup.
  • web/static/price-quality.js stays the only place that knows the 09 scale. Any new colour-coded quality/price signal adds a branch there keyed on isAccessible(), never a local reimplementation, and its accessible form must be shape/text, not another hue.
  • The pricing-tab tables have a mode-dependent column count (8 / 9). index.html guards the <th>, the empty-state colspan, the tfoot "Итого:" colspan and the JS row template on ACCESSIBLE_MODE / {{ eq .IndicatorMode "accessible" }}. CSV export is untouched — it reads data-* row attributes, not cell positions.
  • New routes GET/PUT /api/settings/ui, registered in both the normal and setup-mode route sets in cmd/qfs/main.go. render() nil-guards localDB for tests.