71 lines
16 KiB
Markdown
71 lines
16 KiB
Markdown
# Palette readability audit and bounded candidate decision
|
||
|
||
Independent source audit, 2026-09-12. Reviewed production source at `d61f1f38779ab70a77efcde523ae31c432aefcfd`; root owns #26, integration, compiler gates and native acceptance. No production edits, build, UI interaction, or Gitea mutation performed. Local calculator was executed; neither proposed palette was rendered in the application.
|
||
|
||
## Requirement and benchmark
|
||
|
||
[User issue #26](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/26) asks for graphical elements to display clearly and sharply on monitors with limited color space. Current evidence establishes low source contrast and fine geometry; it does **not** establish a particular monitor gamut, calibration, or failure mechanism. Avoid presenting a saturated replacement color as a cure for an unmeasured display.
|
||
|
||
Use WCAG 2.2 as an explicit engineering benchmark for this native application, not an assertion that this audit establishes web conformance or creates a binding native-app requirement. Normal text should reach 4.5:1 against its actual background; large text has a 3:1 benchmark. These 10–14 px labels should use the normal-text target. Inactive controls and decoration have exceptions, but semantic helper text is not decoration. [W3C contrast explanation](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
|
||
|
||
Meaningful graphical boundaries and visual information identifying controls/states should reach 3:1 against adjacent colors; every decorative divider need not become dark. Assess both sides of an essential border where either side can merge visually. [W3C non-text contrast explanation](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html)
|
||
|
||
Information must survive loss of hue: text, geometry, pattern, symbols or another visible cue should identify meaning. Simply replacing one hue with another does not address hue-dependent status identification. [W3C use-of-color explanation](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html)
|
||
|
||
## Actionable source findings
|
||
|
||
**P1 — Essential unselected geometry uses the decorative LINE token.** `style.rs:10` supplies `(0.86, 0.89, 0.91)`. Architecture node boundaries use it at `diagram.rs:1599`; scenario activation borders use it at `behavior.rs:2004`, and participant/frame boundaries at `behavior.rs:2022`. Against white it is **1.30:1**, against activation fill `#F3F8F8` **1.21:1**. The activation fill itself is only **1.07:1** against white. An unselected narrow execution cannot rely on either signal. Ordinary architecture edges `#909EAD` at `diagram.rs:1176` are **2.57:1** against their `#F6F8FB` canvas (`diagram.rs:1124`). Reproduction: open an unselected architecture node/ordinary link or a nested scenario and identify its boundary without selecting it. Split essential boundary/edge roles from dividers; changing LINE everywhere indiscriminately adds visual noise to all panels.
|
||
|
||
**P1 — Semantic secondary text is below the normal-text benchmark.** MUTED (`style.rs:8`) measures **4.34:1** on white, **3.92:1** on BG and **3.81:1** on state-machine fill. It paints Inspector labels/help (`forms/view_fields.rs:30,154,316`), scenario message labels/details (`behavior.rs:2048,2088,2114`), state kind/action labels (`state_canvas.rs:1480,1511`), and routing warnings (`diagram.rs:1462`). Reproduction: read action/detail text at 100% or identify a message at Fit. Darken the semantic-secondary role; do not achieve hierarchy through reduced opacity of necessary text.
|
||
|
||
**P2 — Category text has additional failures that a MUTED-only change misses.** `diagram.rs:1613` uses kind colors for 11 px headers on the six-percent tint computed at `diagram.rs:1591`. Current actor/tint is **4.48:1** (do not round it to a pass), generic kind/tint **4.42:1**, capability/mission amber/tint **3.43:1**. The other categories in the calculator reach 4.5. Keep category names and existing glyphs; darken the three failing accents or render their text in semantic ink while retaining a color swatch.
|
||
|
||
**P2 — Selected navigation and rows are too dependent on faint fill changes; warning-edge identification explicitly requires hue.** `style.rs:67` selected navigation uses `#DAEEEB` fill (**1.21:1** against white) and `#97CBC3` border (**1.80:1** against white, **1.49:1** against fill). State relation-row selection (`state_canvas.rs:1517,1533`) changes fill/text color without a selection marker. Architecture selection already increases stroke width; state nodes and scenario messages also have useful width changes. However warning links at `diagram.rs:1165` change to amber without a warning symbol; the fallback instruction at `diagram.rs:1448` literally asks to select an amber line. Add a compact visible selection marker for rows/navigation and a warning glyph/label on affected routes, then change that instruction to refer to the cue. Keep reply dashes, include/extend dashes, region dashes, node kind text, collapsed text and pseudostate shapes; they already carry non-hue information and must not be repurposed for focus.
|
||
|
||
**P2 — Inspector/editor colors bypass the five global constants.** `views.rs:1187` constructs the source editor with the default widget style, then installs `highlight::format`; form fields/pick lists similarly use Iced defaults. Installed `iced_widget-0.14.2/src/text_input.rs:1758`, `text_editor.rs:1466`, `pick_list.rs:904` derive backgrounds/borders/placeholders/selection from the extended theme. Border is `background.strong.color`, placeholder is `secondary.base.color`, selection is `primary.weak.color`; none is LINE or MUTED. `iced_core-0.14.0/src/theme/palette.rs:471,506,531` generates them with Oklch deviation and linear-light mixing. Do not substitute guessed hex values for these generated controls. Add small explicit widget-style adapters in existing `style.rs` for essential borders, readable placeholders, visible focus, and selection; test resolved styles in the actual current Iced version. Open/hovered/disabled/checked states and popup menus need review too.
|
||
|
||
**P2 — Source comments and text selection require independent coverage.** Hardcoded lexer colors ignore Theme at `highlight.rs:62`. Comment `#6F7984` is **4.00:1** on editor BG; other ordinary syntax colors reach 4.5. The generated selection fill, computed from Iced's exact linear mix, is approximately `#A0B8B6`. All seven current token colors calculate below 4.5 against this fill (**2.11–3.38:1**). Iced's editor draw (`text_editor.rs:1011,1022,1053`) supplies highlight text and the selection quad without a dedicated selected-text color. Treat this as a source-grounded render risk requiring a native highlighted-selection witness, not as a verified new native failure. The calculator does not model GPU draw batching. Use a lighter selection fill with a clear independent boundary/caret or deliberately adapt selected text; merely strengthening the primary button color may worsen this pair.
|
||
|
||
**Scale limitation — contrast cannot make arbitrarily tiny text readable.** Scenario frame scales geometry/text together (`behavior.rs:1964`); normal message stroke is 1.2 and activation stroke 1 (`behavior.rs:1981,2010,2063`). Native Fit at 66% therefore has approximately 0.79/0.66 logical-pixel strokes and 7.9 px message text before device scaling. Interactive scenario zoom permits 5% (`canvas_workspace.rs:158`), state zoom 15% (`app/state_diagram.rs:137`). At these extremes, palette acceptance must distinguish overview/orientation from detailed reading. Test that boundaries/selection remain traceable and that zooming in recovers labels; do not promise every detail readable at the lowest zoom. If the requirement means detail readability at every allowed scale, geometry/text presentation needs a separate explicit decision.
|
||
|
||
## Existing native evidence inspected
|
||
|
||
Archive: `.local/native-qa/canvas-followup-20260912-01/evidence/`, source `83f53c7c6e277e250f06d2ae6bfa6a7988bd2e4b`. Root is also archiving these under `harness/experiments/canvas-native-83f53c7-20260912/evidence/`.
|
||
|
||
- `arch-delete-saved-0.png`: at 150%, the large unselected entity has a faint enclosing edge, while its name and type stripe are substantially stronger. Inspector label/field-border hierarchy is visible but weak. This is direct evidence of the current presentation, not a calibrated display measurement.
|
||
- `state-selected-0.png`: at 103%, state-machine and region are distinguishable by solid/dashed dark boundaries even though their fill contrast is only 1.09. Selected state gains width and teal; action/help text remains small and pale. This supports retaining state shape/dash cues rather than needlessly darkening all region fills.
|
||
- `sa-nested-minimum-fit-0.png`: a 1100×700-class window shows the scenario at 66%, with slender unselected activation borders and small message labels. Selected dashed reply is much more visible. This demonstrates both color and scale pressure.
|
||
- `oa-outer-activation-selected-0.png` was inspected in the preceding paint audit. Its erased border segments belong to the already separate painter repair; do not attribute those missing pixels to gamut or claim a palette change repairs painter order.
|
||
|
||
No native83 textual-editor screenshot was located in this archive. Source-editor findings therefore remain source/numeric findings. No screenshot was recolored or edited.
|
||
|
||
## Candidate comparison and recommendation
|
||
|
||
Both candidates keep white/light surfaces and opaque semantic strokes. A dark inversion is not needed to test the present hypothesis and would require replacing many hardcoded white backplates/fills; it is not the minimal intervention.
|
||
|
||
| Criterion | A: stronger light palette, retain category accents | B: neutral content, one blue interaction accent |
|
||
|---|---|---|
|
||
| Representative tokens | ink `#202C3A`, secondary `#4B5968`, boundary `#667586`, accent `#00665C`, selection fill `#D9EEE8` | ink `#202020`, secondary `#494949`, boundary `#595959`, accent `#124E96`, selection fill `#E2ECFA` |
|
||
| Numeric margin | secondary/BG 6.49; boundary/white 4.72; boundary/state-machine fill 4.14; accent/white 6.87; accent/selection fill 5.68 | secondary/BG 8.14; boundary/white 7.00; boundary/state-machine fill 6.15; accent/white 8.22; accent/selection fill 6.89 |
|
||
| State distinctions | Retain familiar categories; selection/focus/warning require independent cues. Adjust actor `#754499`, generic `#326B80`, amber `#865B00`; all clear the text benchmark on their six-percent tint. | Make ordinary headers/edges neutral; rely on existing category words/shapes and new status markers. Blue identifies interaction only; warning/danger need words/symbols. |
|
||
| Cognitive load | Preserves current category scan aid. Risk of clutter if strong boundary is applied to every divider; separate roles prevents this. | Simplest luminance hierarchy and least reliance on category hue. Loses an existing category scan aid; higher stroke weight everywhere may dominate dense diagrams. |
|
||
| Migration cost | Small role split plus targeted hardcoded replacements and control adapters. Lexer keeps its working colors; comment darkens and selection fill is controlled. | Similar control adapters, but broader removal/reassignment of category colors and header/edge rendering conventions. More native review for changed visual grouping. |
|
||
|
||
**Recommend A as the first bounded candidate**, with B retained as the meaningful fallback if degraded/native task evaluation still shows ambiguity. This is a reversible design recommendation, not acceptance. A supplies contrast margin without discarding already useful category cues. Do not introduce a theme framework or new preference settings.
|
||
|
||
Minimal roles in existing `style.rs`: canvas/surface/background; primary text; secondary semantic text; decorative divider; essential boundary/edge; interaction accent; selection fill; warning/danger (reuse existing status roles); existing kind-color mapping. Selection and keyboard focus can share a color but must differ geometrically: selected object/row marker versus a spatially separate focus outline. A new color is not required for each widget, hover state or semantic kind. Keep opacity only where justified by layering (modal scrim) or inactive state, not for essential geometry.
|
||
|
||
The audit inventory must include the existing hardcoded modal `rgba(.08,.12,.18,.38)` (`views.rs:374`, `state_view.rs:229`, `hosted/view.rs:35`) and context-menu shadow `rgba(.1,.15,.2,.17)` (`context_menu.rs:682`). These are layering/decorative uses. Iced button disabled styling scales background/text alpha to 0.5 (`button.rs:748`), and text-button hovered styling uses text alpha 0.8 (`button.rs:684`; custom nav restores INK when enabled). Keep disabled meaning explicit through operability and visible state, and inspect composited colors on actual backing surfaces. Do not apply an opaque-source contrast ratio to an alpha-composited pixel.
|
||
|
||
## Minimal evaluator suite before accepting A
|
||
|
||
1. **Resolved-color contract:** execute sRGB checks on production style outputs, not only copied candidate hex values. Cover secondary text, every kind/header tint, all syntax classes on ordinary and selected editor backgrounds, field placeholder/value/selection, navigation, checkbox/pick-list states, essential canvas boundaries on both adjacent fills, warning/danger. Require 4.5 text/3 meaningful graphics as stated benchmark; retain a margin for small/thin strokes. Exclude decoration by named role, not broad file exemptions. Include a negative control asserting the old MUTED/BG, LINE/activation and ordinary edge/canvas pairs fail.
|
||
2. **Rendered witnesses:** real WGPU output of the same OA/SA nested scenario, architecture edges/ports, state region/actions/pseudostates, Inspector inputs/open pick list, and source tokens with selected text. Inspect ordinary, selected, keyboard-focused, hovered, disabled, warning/error and modal states. Sample interior color and continuous border runs; check masks/order do not remove geometry. Existing painter continuity evaluator remains necessary and must be adapted if its selected-color predicate is hardcoded.
|
||
3. **Native tasks:** at 100%, native minimum window, Fit, and actual lowest allowed zoom, locate a nested activation and its Call/Reply, follow an ordinary architecture route, identify state action/region, edit/revert Inspector text, select/copy highlighted source, locate warning route and transfer keyboard focus. Require ordinary edges and current focus/selection to remain identifiable without hover. Treat lowest zoom as overview unless a separate detail requirement is adopted. Record scale/DPI, source commit and app build.
|
||
4. **Loss-of-color/degradation falsifiers:** on separately generated analysis output (leave original native witnesses intact), convert to luminance grayscale, quantize channels, and apply a documented moderate luminance-range compression/veiling approximation. Repeat selected/focused/warning/disabled identification; mark simulation parameters and failures. Grayscale preserves luminance contrast but removes hue. These stress tests are not a simulation of all limited-gamut monitors and do not establish arbitrary-monitor proof. Validate on the user's problematic physical display if available; do not block the useful bounded candidate on unknowable hardware details.
|
||
5. **Scope guard:** preserve hit targets, layer order, correlation/activation geometry, keyboard ownership and existing dash/shape semantics. Passing color calculations cannot replace native interaction evidence or close #26 by itself.
|
||
|
||
## Reproduction
|
||
|
||
`palette-contrast-20260912.py` next to this report is dependency-free Python. It retains source float constants, linearizes sRGB with the 0.04045 threshold, uses luminance weights 0.2126/0.7152/0.0722, and computes `(Llighter+0.05)/(Ldarker+0.05)`. Source tint and Iced selection mixing are explicit. Executed output is `palette-contrast-20260912.json`; six decimals are retained and threshold comparisons use unrounded values. Hex labels for float constants are display approximations. This measures specified opaque source pairs, not anti-aliasing, color-management, physical monitor performance or a new native palette.
|