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

64 lines
3.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](2026-07-10-world-pricelist-fallback.md));
- the red "contaminated total" in the pricing footer
([2026-07-24-pricing-total-world-share.md](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.