Record palette decisions and passing rendered selection gate
This commit is contained in:
@@ -74,3 +74,4 @@ harness/experiments/canvas-native-83f53c7-20260912/** -text whitespace=-blank-at
|
||||
harness/experiments/nested-lifecycle-union-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/canvas-native-d61f1f3-20260912/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/lifecycle-fixture-preflight-20260913/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/experiments/palette-readability-20260913/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
|
||||
@@ -104,6 +104,13 @@ Call/Reply tests pass on 536 unchanged inputs, including rendered border continu
|
||||
These 51 targeted checks do not establish full-suite or native acceptance for the
|
||||
combined source. [Integration evidence](release-lifecycle-activation-union-20260913.md).
|
||||
|
||||
Source **afa74a305980f611437dd3aa2f937d36e9266cfe** adds stronger palette roles,
|
||||
essential canvas/process boundaries and readable read-only input selection.
|
||||
All ten targeted palette tests pass on 538 unchanged inputs, including actual
|
||||
WGPU selection/copy across an enabled-to-read-only field rebuild. Full combined
|
||||
and native palette acceptance remain open; the custom overlay residuals and
|
||||
monitor limitations are recorded in [palette evidence](release-palette-readability-20260913.md).
|
||||
|
||||
[Native follow-up evidence](release-canvas-native-83f53c7-20260912.md) records
|
||||
the normally closed source83 trial, observed defect, minimum-size layout and
|
||||
the boundary between state and scenario keyboard policies.
|
||||
|
||||
@@ -363,5 +363,20 @@
|
||||
"file": "atomic-aadc639.result.json",
|
||||
"bytes": 458,
|
||||
"sha256": "26e1cf5e541947055d9c62cca4b71ac1cf102ac9b73eab3ff7f597ab38a813a3"
|
||||
},
|
||||
{
|
||||
"file": "palette-afa74a3.log",
|
||||
"bytes": 1285,
|
||||
"sha256": "dd9acc2811d3bfccdf0e2fc4061ceccfd1b42a7d429c271c62cb9dccf194c779"
|
||||
},
|
||||
{
|
||||
"file": "palette-afa74a3.inputs.json",
|
||||
"bytes": 100254,
|
||||
"sha256": "d7dc0bd3b642e50c2796035696fc6da5bb0cbf07e680e15528c49229544c5583"
|
||||
},
|
||||
{
|
||||
"file": "palette-afa74a3.result.json",
|
||||
"bytes": 408,
|
||||
"sha256": "06cea4241ff6ccbc2fc50c1c1ae95893d15e09c8f3303eb9c551ae31e1c1ff80"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\Users\User\Documents\Git\ARCADIA-Studio\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 7m 20s
|
||||
Running unittests src\main.rs (target\debug\deps\arcadia_studio-e358a5ed59b1de41.exe)
|
||||
|
||||
running 10 tests
|
||||
test style::tests::button_pair_couplings_cover_hover_and_disabled_states ... ok
|
||||
test style::tests::conditional_input_styles_preserve_readonly_selected_value_contrast ... ok
|
||||
test style::tests::default_checkbox_styles_keep_checks_and_outlines_distinct ... ok
|
||||
test style::tests::default_input_styles_keep_ordinary_values_and_visible_selection ... ok
|
||||
test style::tests::default_pick_list_and_menu_styles_cover_open_and_selected_states ... ok
|
||||
test style::tests::ordinary_editable_editor_styles_keep_their_own_selected_value_readable ... ok
|
||||
test style::tests::readonly::mounted_readonly_input_keeps_selection_copy_and_readable_paint ... ok
|
||||
test style::tests::selected_navigation_adds_a_visible_outline_and_preserves_disabled_ink ... ok
|
||||
test style::tests::semantic_text_and_essential_boundaries_have_contrast_on_canvas_surfaces ... ok
|
||||
test style::tests::source_editor_syntax_and_fallback_remain_readable_during_selection ... ok
|
||||
|
||||
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 862 filtered out; finished in 3.85s
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"source": "afa74a305980f611437dd3aa2f937d36e9266cfe",
|
||||
"start": "2026-09-13T00:29:47.773169+00:00",
|
||||
"end": "2026-09-13T00:37:15.893963+00:00",
|
||||
"exit": 0,
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--locked",
|
||||
"-p",
|
||||
"oa-desktop",
|
||||
"--bin",
|
||||
"arcadia-studio",
|
||||
"style::tests",
|
||||
"--",
|
||||
"--test-threads=1"
|
||||
],
|
||||
"inputs": "palette-afa74a3.inputs.json",
|
||||
"changed_inputs": []
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
"""Preserve the bounded palette decisions and reviews, without rerunning them."""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
root = next(parent for parent in Path(__file__).resolve().parents
|
||||
if (parent / 'Cargo.toml').is_file() and (parent / '.git').exists())
|
||||
out = root / 'harness/experiments/palette-readability-20260913'
|
||||
names = [
|
||||
'palette-readability-audit-20260912.md',
|
||||
'palette-contrast-20260912.py', 'palette-contrast-20260912.json',
|
||||
'palette-readability-independent-critique-20260912.md',
|
||||
'palette-readability-independent-calculation-20260912.json',
|
||||
'palette-selection-remedy-20260912.md',
|
||||
'palette-selection-remedy-20260912.py', 'palette-selection-remedy-20260912.json',
|
||||
'palette-foundation-source-handoff-20260912.md',
|
||||
'palette-foundation-independent-review-20260913.md',
|
||||
'palette-canvas-handoff-20260913.md',
|
||||
'palette-readonly-correction-source-handoff-20260913.md',
|
||||
'palette-correction-independent-review-20260913.md',
|
||||
'routing-warning-selector-scope-20260913.md',
|
||||
'palette-integration-decision-20260913.md',
|
||||
]
|
||||
mode = sys.argv[1]
|
||||
if mode == 'create':
|
||||
assert not out.exists(), 'Never overwrite archived evidence'
|
||||
sources = {name: root / '.local' / name for name in names}
|
||||
sources['archive.py'] = Path(__file__)
|
||||
assert all(path.is_file() for path in sources.values())
|
||||
out.mkdir(parents=True)
|
||||
rows = []
|
||||
for name, path in sources.items():
|
||||
raw = path.read_bytes()
|
||||
with (out / name).open('xb') as stream:
|
||||
stream.write(raw)
|
||||
rows.append({'path': name, 'source': path.relative_to(root).as_posix(),
|
||||
'bytes': len(raw), 'sha256': hashlib.sha256(raw).hexdigest()})
|
||||
record = {'scope': 'Historical candidate decisions, arithmetic and source review; native acceptance is separate.',
|
||||
'limitations': 'Original local source/command references retain their historical checkout layout. Do not run copied calculators as portable build gates. These files do not establish monitor acceptance.',
|
||||
'files': rows}
|
||||
with (out / 'manifest.json').open('x', encoding='utf-8', newline='\n') as stream:
|
||||
json.dump(record, stream, indent=2)
|
||||
stream.write('\n')
|
||||
else:
|
||||
assert mode in ['verify', 'verify-index']
|
||||
rows = json.loads((out / 'manifest.json').read_bytes())['files']
|
||||
for row in rows:
|
||||
raw = (out / row['path']).read_bytes()
|
||||
assert len(raw) == row['bytes']
|
||||
assert hashlib.sha256(raw).hexdigest() == row['sha256']
|
||||
if mode == 'verify-index':
|
||||
rel = (out / row['path']).relative_to(root).as_posix()
|
||||
assert raw == subprocess.check_output(['git', 'show', ':' + rel], cwd=root)
|
||||
print(json.dumps({'mode': mode, 'verified_payloads': len(rows), 'payload_bytes': sum(row['bytes'] for row in rows)}))
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"scope": "Historical candidate decisions, arithmetic and source review; native acceptance is separate.",
|
||||
"limitations": "Original local source/command references retain their historical checkout layout. Do not run copied calculators as portable build gates. These files do not establish monitor acceptance.",
|
||||
"files": [
|
||||
{
|
||||
"path": "palette-readability-audit-20260912.md",
|
||||
"source": ".local/palette-readability-audit-20260912.md",
|
||||
"bytes": 16228,
|
||||
"sha256": "1039a31206f7fa702b4eae310c83268c3c7a063c55a38d8aab2dc3c248b20d1a"
|
||||
},
|
||||
{
|
||||
"path": "palette-contrast-20260912.py",
|
||||
"source": ".local/palette-contrast-20260912.py",
|
||||
"bytes": 3919,
|
||||
"sha256": "f81d4b47cb15998adb33a3115b4e19fad96385238371860de771bb9b46baeb36"
|
||||
},
|
||||
{
|
||||
"path": "palette-contrast-20260912.json",
|
||||
"source": ".local/palette-contrast-20260912.json",
|
||||
"bytes": 13411,
|
||||
"sha256": "0b751a07823a7259d8a549e72febdb2fe2084211eb9cd0504cdbd93d173df8dd"
|
||||
},
|
||||
{
|
||||
"path": "palette-readability-independent-critique-20260912.md",
|
||||
"source": ".local/palette-readability-independent-critique-20260912.md",
|
||||
"bytes": 12465,
|
||||
"sha256": "1eb5f7436010600effb7c9de0c3c23ffa84efe1f70e527d7f926cfa6230d1cd0"
|
||||
},
|
||||
{
|
||||
"path": "palette-readability-independent-calculation-20260912.json",
|
||||
"source": ".local/palette-readability-independent-calculation-20260912.json",
|
||||
"bytes": 1972,
|
||||
"sha256": "7f1ccd703eaa03f01dd64ca469df859845c9fd247e632e217652662b95284002"
|
||||
},
|
||||
{
|
||||
"path": "palette-selection-remedy-20260912.md",
|
||||
"source": ".local/palette-selection-remedy-20260912.md",
|
||||
"bytes": 15444,
|
||||
"sha256": "28684c54ea8710426ea5d60c206cfd3c8ec513851ea42e42450f1e8d518fd7b9"
|
||||
},
|
||||
{
|
||||
"path": "palette-selection-remedy-20260912.py",
|
||||
"source": ".local/palette-selection-remedy-20260912.py",
|
||||
"bytes": 3048,
|
||||
"sha256": "7ec45627fe02368c2239d3ecc75ac5f32a8bec155522718b291f78bd19c0c160"
|
||||
},
|
||||
{
|
||||
"path": "palette-selection-remedy-20260912.json",
|
||||
"source": ".local/palette-selection-remedy-20260912.json",
|
||||
"bytes": 5836,
|
||||
"sha256": "b05890b80887fa5dbadd042f207298fb4ee5243dba4eadd6900bf883b48da47e"
|
||||
},
|
||||
{
|
||||
"path": "palette-foundation-source-handoff-20260912.md",
|
||||
"source": ".local/palette-foundation-source-handoff-20260912.md",
|
||||
"bytes": 8355,
|
||||
"sha256": "4b6ab222bbbb431065baa256a358a166785896202157463e7afb2c3359817921"
|
||||
},
|
||||
{
|
||||
"path": "palette-foundation-independent-review-20260913.md",
|
||||
"source": ".local/palette-foundation-independent-review-20260913.md",
|
||||
"bytes": 10364,
|
||||
"sha256": "d64d92ec2ecab5aa5b92ac612c15f13bba383a0a28dc5863e3d610acd8ff3f73"
|
||||
},
|
||||
{
|
||||
"path": "palette-canvas-handoff-20260913.md",
|
||||
"source": ".local/palette-canvas-handoff-20260913.md",
|
||||
"bytes": 9096,
|
||||
"sha256": "919b2d0407369a9dcc64236171bb35e0c997a7e1c6f6f12703885e9c9b918559"
|
||||
},
|
||||
{
|
||||
"path": "palette-readonly-correction-source-handoff-20260913.md",
|
||||
"source": ".local/palette-readonly-correction-source-handoff-20260913.md",
|
||||
"bytes": 5878,
|
||||
"sha256": "fc0d3e5eca653eb2e78dd2aa70de72cdedd699f60455fad519a43ed1efbdf03a"
|
||||
},
|
||||
{
|
||||
"path": "palette-correction-independent-review-20260913.md",
|
||||
"source": ".local/palette-correction-independent-review-20260913.md",
|
||||
"bytes": 13441,
|
||||
"sha256": "7024b7876f6a91194e93d59576e241515c373bdf551c62ff642fbb0b0c3f2350"
|
||||
},
|
||||
{
|
||||
"path": "routing-warning-selector-scope-20260913.md",
|
||||
"source": ".local/routing-warning-selector-scope-20260913.md",
|
||||
"bytes": 11223,
|
||||
"sha256": "1b020af92e16c2b29e9ebbe8abe194d251e0e6fcc3208d5e5ac8bed2997575c1"
|
||||
},
|
||||
{
|
||||
"path": "palette-integration-decision-20260913.md",
|
||||
"source": ".local/palette-integration-decision-20260913.md",
|
||||
"bytes": 3053,
|
||||
"sha256": "ab38aad782eeacbcdf22095bc7a158918cdf9e778326aff316892521ce895b19"
|
||||
},
|
||||
{
|
||||
"path": "archive.py",
|
||||
"source": ".local/archive-palette-design-20260913.py",
|
||||
"bytes": 2844,
|
||||
"sha256": "d9f7b626d84c0a4386cfca11b2e54ee30618ed2dc54e7ea77c0e5a337e8285e3"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
# Essential canvas palette consumers — source-only handoff
|
||||
|
||||
Commit **`092f5244b9264b53bccea884a1a4fe9d860f2ad2`**, branch `codex/palette-canvas-956atf-20260913`, isolated worktree `.local/palette-canvas-20260913`, base **`aadc6396d39ff336d05381d58d545093d3f37a73`**. The worktree is clean. Root owns #26, synthesis, integration, compiler/native gates and Gitea. Only the four delegated source files changed; no root tracked files or other workers' files were edited.
|
||||
|
||||
Read the palette audit and independent critique before editing. This patch implements essential boundary consumers and the selected state-row cue. As explicitly agreed by root during this task, the architecture warning cue remains a proposal below; existing warning color and “amber line” guidance stay coherent and unchanged.
|
||||
|
||||
## Implemented sites
|
||||
|
||||
- `behavior.rs:2077,2094`: unselected typed activation borders, participant/action cards, fragment/branch outlines use `style::BOUNDARY` instead of decorative LINE. Existing fills, widths, painter order, message/arrow/label order, typed geometry and hit priority are unchanged.
|
||||
- `diagram.rs:1176,1602`: ordinary architecture edge color `#909EAD` and unselected architecture node/container boundaries now use BOUNDARY. Selected/category/trace/warning paths retain their existing roles. Grid dots remain LINE. No path, port, arrowhead, label bounds, dash pattern or selection handle changes.
|
||||
- `behavior/scenario_lifecycle/render.rs:243,245,265`: editing guides use TEAL when selected and BOUNDARY otherwise; definite lifetime lines use the same roles. The previously very pale dotted guide must remain findable in absent/unresolved intervals. Its dotted 1-unit geometry remains distinct from the solid 2-unit Alive interval; '?' and the existing legend remain intact. Capsule/X painting, clipping, hits and lifetime semantics are unchanged.
|
||||
- `behavior/state_canvas.rs:1517,1533,1568`: selected StateAction and internal-transition relation rows retain their existing fill and gain an inset 1.5-unit TEAL rectangle. This supplies a visible shape change when hue is lost. The outline stays within the existing row, away from text and separator; row dimensions, layout, ordering, relation identity and hit bounds do not change. A defensive guard skips the outline for a row no larger than two units, avoiding negative rectangle dimensions for minimum/degenerate geometry. Existing state node outlines already use INK/TEAL; their header and guidance dividers remain LINE.
|
||||
|
||||
**Dependency:** the separate foundation worker must supply `pub const BOUNDARY: iced::Color` in `style.rs`. It is intentionally not defined or copied into this worktree. The isolated commit alone is not claimed to compile; combine with the reviewed foundation first. All other colors remain the existing style roles or existing fills.
|
||||
|
||||
## Verification and acceptance limits
|
||||
|
||||
Executed standalone rustfmt with edition 2024 and `skip_children=true`, Git whitespace/diff checks, exact four-file ownership review and commit/status checks. No Cargo, rustc, tests, app launch/input, screenshots or Gitea writes. No test source was changed or removed; no mirror-of-implementation unit tests were added for the simple paint-role substitutions.
|
||||
|
||||
Root should retain the existing activation WGPU continuity, shoulder/endpoint selection and lifecycle marker/clip tests. State regressions include `typed_state_actions_are_named_selectable_rows_before_internal_transitions_and_regions`, `internal_rows_and_pseudostate_hits_use_shared_persisted_geometry`, and selected-endpoint reconnect tests. Diagram route/pin/label/port handles and source/SVG geometry checks should remain unchanged. All are **unrun for this commit**.
|
||||
|
||||
After foundation integration, actual resolved-color tests must cover BOUNDARY against white, six-percent container tints, activation fill and state fills; do not assume the proposed numeric token is the final integrated value. Native/WGPU witnesses must inspect ordinary edges, selected/unselected narrow activations, absence/Alive/unresolved guide distinctions and selected StateAction/internal rows in grayscale, at 100%, Fit/minimum window and overview zoom. Existing selected-border pixel predicates must continue measuring continuity rather than merely a convenient RGB identity. No palette or monitor acceptance is claimed.
|
||||
|
||||
Main visual risk: stronger dotted guides and stronger ordinary boundaries increase visual density. They preserve the existing 1-versus-2-unit dotted/solid lifetime distinction, but its legibility requires the combined native witness. The selected-row outline is additional paint inside existing geometry; it is not a font, zoom or layout remedy.
|
||||
|
||||
## Warning cue proposal: actual current behavior
|
||||
|
||||
`diagram.rs:1165` colors an edge amber when its derived warnings contain something other than `PromotedEndpoints`. Selected/highlighted edges instead use TEAL, so hue alone does not identify that selected edge's warning. `diagram.rs:1424` paints a fixed 30-unit screen-space warning strip: selected non-suspended route gets its first warning text; otherwise it counts actionable warning routes and says to select an amber line. Suspended custom routes defer to the fixed host hint explaining expansion. These are actual derived routing warnings; do not replace them with unrelated model validation warnings.
|
||||
|
||||
`routing.rs:46,1269` owns `EdgeLabelGeometry` and its semantic text, exact bounds and placement. Empty-label routes have zero label bounds. Nonempty labels measure their present text and avoid nodes, prior labels and visible strokes using a bounded search; authored offsets stay exact. `diagram.rs:1208` paints these same bounds, and `:372` uses them for hit testing. Association-end labels and arrows have separate geometry. Appending `!` or a triangle at the current label edge would escape already measured/collision-checked space, while an on-route backplate could erase another path or an endpoint arrow. This palette-only patch therefore does not do that.
|
||||
|
||||
| Candidate | Benefit | Cost / falsifier |
|
||||
|---|---|---|
|
||||
| On-route `!` glyph with a contrasting halo | Immediate spatial association, including in screenshots | Needs an explicit shared placement/bounds contract for unlabeled, short, coincident, reversed, self and authored-label routes; must avoid both endpoint arrow zones and other paths/labels. Placement/hits/export consistency exceed this four-file color-consumer patch. Test an obstacle/no-clear-path case with only a short endpoint segment and a crowded label before choosing it. |
|
||||
| Explicit selectable warning entries through the existing interaction host | Names the affected relation and actual warning without hue; adds no paint over model geometry or arrows; handles hidden labels and coincident routes | Requires a small host/menu wiring change outside current ownership and a source-context-bound snapshot of the actual visible routed scene. It is a discoverable list rather than an on-route glyph; selection must visibly identify the corresponding edge. |
|
||||
|
||||
**Recommend the selectable-entry candidate for the next bounded task.** The existing architecture host already reserves a fixed-height hint strip outside the measured canvas (`views.rs:569–603`) and uses the shared menu infrastructure (`model_menu.rs:35,47`, `context_menu::Menu`). A “Routing warnings (N)” control in that reserved strip can open entries such as “Warning · Pressure channel — No clear path”. Include stable alias or endpoint names when display names collide. Selecting an entry should use the established relation selection action (`Message::Select(id)`, `app.rs:2132`) through the existing context-bound menu invocation path; root must confirm the existing select handler's draft/focus safeguards apply. Preserve expand-endpoint guidance for suspended routes.
|
||||
|
||||
Requirements before implementing that proposal: obtain warnings from the same settled `RoutedView` that is displayed, rather than running an independent router every view rebuild; bind entries to document/diagram/revision/epoch and invalidate on context changes using the existing menu protections; keep the fixed strip height constant while dragging/resizing; do not steal focus from an active text edit or commit a pending route gesture. Rename the old “amber line” instruction only when the control actually exists. A plain unspecialized current `model_menu::Request` does not already carry a warning list, so this is proposed explicit wiring, not a claimed existing capability.
|
||||
|
||||
Minimal future evaluators: two warning edges with duplicate names; an unlabeled warning edge; a selected warning edge whose line is TEAL; an ordinary non-warning edge; `PromotedEndpoints`/suspended custom routing; keyboard open/select/Escape; stale menu after switching document/view or editing geometry; and unchanged routes/source/history plus unchanged measured canvas size. This candidate addresses warning discoverability without expanding the palette patch into a label-placement policy. Warning non-hue identification remains **unfinished** until that work is implemented and tested.
|
||||
@@ -0,0 +1,569 @@
|
||||
[
|
||||
{
|
||||
"foreground": "INK",
|
||||
"background": "white",
|
||||
"fg_hex": "#212E40",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 13.744668,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "MUTED",
|
||||
"background": "white",
|
||||
"fg_hex": "#6E7A8C",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 4.336885,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "MUTED",
|
||||
"background": "BG",
|
||||
"fg_hex": "#6E7A8C",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 3.923298,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "MUTED",
|
||||
"background": "machine",
|
||||
"fg_hex": "#6E7A8C",
|
||||
"bg_hex": "#EAF1F8",
|
||||
"ratio": 3.807385,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "TEAL",
|
||||
"background": "white",
|
||||
"fg_hex": "#007A6E",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 5.214623,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "TEAL",
|
||||
"background": "rowfill",
|
||||
"fg_hex": "#007A6E",
|
||||
"bg_hex": "#E0F7F2",
|
||||
"ratio": 4.657385,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "LINE",
|
||||
"background": "white",
|
||||
"fg_hex": "#DBE3E8",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 1.298757,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "LINE",
|
||||
"background": "activation",
|
||||
"fg_hex": "#DBE3E8",
|
||||
"bg_hex": "#F3F8F8",
|
||||
"ratio": 1.211763,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "edge",
|
||||
"background": "canvas",
|
||||
"fg_hex": "#909EAD",
|
||||
"bg_hex": "#F6F8FB",
|
||||
"ratio": 2.570112,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "navfill",
|
||||
"background": "white",
|
||||
"fg_hex": "#DAEEEB",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 1.206167,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "navborder",
|
||||
"background": "navfill",
|
||||
"fg_hex": "#97CBC3",
|
||||
"bg_hex": "#DAEEEB",
|
||||
"ratio": 1.494946,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "navborder",
|
||||
"background": "white",
|
||||
"fg_hex": "#97CBC3",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 1.803154,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "activation",
|
||||
"background": "white",
|
||||
"fg_hex": "#F3F8F8",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 1.071792,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "region",
|
||||
"background": "machine",
|
||||
"fg_hex": "#F7FAFD",
|
||||
"bg_hex": "#EAF1F8",
|
||||
"ratio": 1.087404,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "entity",
|
||||
"background": "entity_tint",
|
||||
"fg_hex": "#2966B5",
|
||||
"bg_hex": "#F2F6FB",
|
||||
"ratio": 5.278644,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "actor",
|
||||
"background": "actor_tint",
|
||||
"fg_hex": "#9459B2",
|
||||
"bg_hex": "#F9F5FA",
|
||||
"ratio": 4.483591,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "TEAL",
|
||||
"background": "TEAL_tint",
|
||||
"fg_hex": "#007A6E",
|
||||
"bg_hex": "#F0F7F6",
|
||||
"ratio": 4.802033,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "amber",
|
||||
"background": "amber_tint",
|
||||
"fg_hex": "#B8781A",
|
||||
"bg_hex": "#FBF7F1",
|
||||
"ratio": 3.434992,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "scenario",
|
||||
"background": "scenario_tint",
|
||||
"fg_hex": "#A65466",
|
||||
"bg_hex": "#FAF5F6",
|
||||
"ratio": 4.756802,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "state",
|
||||
"background": "state_tint",
|
||||
"fg_hex": "#6163AD",
|
||||
"bg_hex": "#F6F6FA",
|
||||
"ratio": 4.960885,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "other",
|
||||
"background": "other_tint",
|
||||
"fg_hex": "#407A8F",
|
||||
"bg_hex": "#F4F7F8",
|
||||
"ratio": 4.416088,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "keyword",
|
||||
"background": "BG",
|
||||
"fg_hex": "#763EA0",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 6.402935,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "keyword",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#763EA0",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 3.375567,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "identifier",
|
||||
"background": "BG",
|
||||
"fg_hex": "#145D8B",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 6.400071,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "identifier",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#145D8B",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 3.374057,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "string",
|
||||
"background": "BG",
|
||||
"fg_hex": "#18714F",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.406507,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "string",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#18714F",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 2.850259,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "number",
|
||||
"background": "BG",
|
||||
"fg_hex": "#A34E1E",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.182304,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "number",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#A34E1E",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 2.732062,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "comment",
|
||||
"background": "BG",
|
||||
"fg_hex": "#6F7984",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 4.004465,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "comment",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#6F7984",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 2.111116,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "error",
|
||||
"background": "BG",
|
||||
"fg_hex": "#BA263E",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.531122,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "error",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#BA263E",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 2.915956,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "punctuation",
|
||||
"background": "BG",
|
||||
"fg_hex": "#5B6575",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.33158,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "punctuation",
|
||||
"background": "editor_selection",
|
||||
"fg_hex": "#5B6575",
|
||||
"bg_hex": "#A0B8B6",
|
||||
"ratio": 2.810759,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": false
|
||||
},
|
||||
{
|
||||
"foreground": "diagnostic_error",
|
||||
"background": "BG",
|
||||
"fg_hex": "#AE2E40",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.819255,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "diagnostic_warning",
|
||||
"background": "BG",
|
||||
"fg_hex": "#915D17",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.023012,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_ink",
|
||||
"background": "white",
|
||||
"fg_hex": "#202C3A",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 14.162532,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_secondary",
|
||||
"background": "BG",
|
||||
"fg_hex": "#4B5968",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 6.488122,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_boundary",
|
||||
"background": "white",
|
||||
"fg_hex": "#667586",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 4.715161,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_boundary",
|
||||
"background": "machine",
|
||||
"fg_hex": "#667586",
|
||||
"bg_hex": "#EAF1F8",
|
||||
"ratio": 4.139478,
|
||||
"normal_text_4_5": false,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_accent",
|
||||
"background": "white",
|
||||
"fg_hex": "#00665C",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 6.873775,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_accent",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#00665C",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 5.67754,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_ink",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#202C3A",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 11.697844,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_ink",
|
||||
"background": "white",
|
||||
"fg_hex": "#202020",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 16.293255,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_secondary",
|
||||
"background": "BG",
|
||||
"fg_hex": "#494949",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 8.144559,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_boundary",
|
||||
"background": "white",
|
||||
"fg_hex": "#595959",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 7.004729,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_boundary",
|
||||
"background": "machine",
|
||||
"fg_hex": "#595959",
|
||||
"bg_hex": "#EAF1F8",
|
||||
"ratio": 6.149507,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_accent",
|
||||
"background": "white",
|
||||
"fg_hex": "#124E96",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 8.216358,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_accent",
|
||||
"background": "B_selection",
|
||||
"fg_hex": "#124E96",
|
||||
"bg_hex": "#E2ECFA",
|
||||
"ratio": 6.890925,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "B_ink",
|
||||
"background": "B_selection",
|
||||
"fg_hex": "#202020",
|
||||
"bg_hex": "#E2ECFA",
|
||||
"ratio": 13.664887,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_amber",
|
||||
"background": "white",
|
||||
"fg_hex": "#865B00",
|
||||
"bg_hex": "#FFFFFF",
|
||||
"ratio": 5.982734,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_amber",
|
||||
"background": "BG",
|
||||
"fg_hex": "#865B00",
|
||||
"bg_hex": "#F1F4F7",
|
||||
"ratio": 5.412192,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_actor",
|
||||
"background": "A_actor_tint",
|
||||
"fg_hex": "#754499",
|
||||
"bg_hex": "#F7F4F9",
|
||||
"ratio": 6.317299,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_other",
|
||||
"background": "A_other_tint",
|
||||
"fg_hex": "#326B80",
|
||||
"bg_hex": "#F3F6F7",
|
||||
"ratio": 5.448667,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_amber",
|
||||
"background": "A_amber_tint",
|
||||
"fg_hex": "#865B00",
|
||||
"bg_hex": "#F8F5F0",
|
||||
"ratio": 5.503209,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "keyword",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#763EA0",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 5.846159,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "identifier",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#145D8B",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 5.843544,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "string",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#18714F",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 4.936376,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "number",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#A34E1E",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 4.731669,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "A_secondary",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#4B5968",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 5.923938,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "error",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#BA263E",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 5.050156,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
},
|
||||
{
|
||||
"foreground": "punctuation",
|
||||
"background": "A_selection",
|
||||
"fg_hex": "#5B6575",
|
||||
"bg_hex": "#D9EEE8",
|
||||
"ratio": 4.867964,
|
||||
"normal_text_4_5": true,
|
||||
"meaningful_graphic_3": true
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Source-color analysis only; no application rendering or screenshot modification.
|
||||
Run: python .local/palette-contrast-20260912.py
|
||||
WCAG sRGB definition: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
|
||||
Float source constants are retained. Hex labels are rounded display conveniences.
|
||||
"""
|
||||
import json
|
||||
|
||||
def rgb(h):
|
||||
return tuple(int(h[i:i+2], 16)/255 for i in (0, 2, 4))
|
||||
|
||||
def linear(c):
|
||||
return c/12.92 if c <= .04045 else ((c+.055)/1.055)**2.4
|
||||
|
||||
def encoded(c):
|
||||
return 12.92*c if c <= .0031308 else 1.055*c**(1/2.4)-.055
|
||||
|
||||
def lum(c):
|
||||
return sum(w*linear(v) for w, v in zip((.2126, .7152, .0722), c))
|
||||
|
||||
def contrast(a, b):
|
||||
x, y = sorted((lum(a), lum(b)))
|
||||
return (y+.05)/(x+.05)
|
||||
|
||||
def hexcode(c):
|
||||
return '#' + ''.join(f'{round(v*255):02X}' for v in c)
|
||||
|
||||
C = dict(white=(1,1,1), BG=(.945,.956,.969), INK=(.13,.18,.25),
|
||||
MUTED=(.43,.48,.55), TEAL=(0,.48,.43), LINE=(.86,.89,.91),
|
||||
amber=(.72,.47,.10), entity=(.16,.4,.71), actor=(.58,.35,.7),
|
||||
scenario=(.65,.33,.4), state=(.38,.39,.68), other=(.25,.48,.56))
|
||||
for name, h in dict(canvas='F6F8FB', activation='F3F8F8', region='F7FAFD',
|
||||
machine='EAF1F8', navfill='DAEEEB', navborder='97CBC3',
|
||||
rowfill='E0F7F2', edge='909EAD', trace='866BA8',
|
||||
keyword='763EA0', identifier='145D8B', string='18714F',
|
||||
number='A34E1E', comment='6F7984', error='BA263E',
|
||||
punctuation='5B6575', diagnostic_error='AE2E40',
|
||||
diagnostic_warning='915D17').items():
|
||||
C[name] = rgb(h)
|
||||
# iced_core 0.14 palette::Primary::generate and mix use linear-light blending.
|
||||
C['editor_selection'] = tuple(encoded(.6*linear(a)+.4*linear(b))
|
||||
for a,b in zip(C['TEAL'], C['BG']))
|
||||
pairs = [('INK','white'), ('MUTED','white'), ('MUTED','BG'),
|
||||
('MUTED','machine'), ('TEAL','white'), ('TEAL','rowfill'),
|
||||
('LINE','white'), ('LINE','activation'), ('edge','canvas'),
|
||||
('navfill','white'), ('navborder','navfill'), ('navborder','white'),
|
||||
('activation','white'), ('region','machine')]
|
||||
for name in ('entity','actor','TEAL','amber','scenario','state','other'):
|
||||
C[name+'_tint'] = tuple(.94+.06*v for v in C[name])
|
||||
pairs.append((name,name+'_tint'))
|
||||
for name in ('keyword','identifier','string','number','comment','error','punctuation'):
|
||||
pairs += [(name,'BG'), (name,'editor_selection')]
|
||||
for name in ('diagnostic_error','diagnostic_warning'):
|
||||
pairs.append((name,'BG'))
|
||||
# Candidate A retains category accents. Candidate B uses neutral content and blue selection.
|
||||
for name,h in dict(A_ink='202C3A', A_secondary='4B5968', A_boundary='667586',
|
||||
A_accent='00665C', A_selection='D9EEE8', A_amber='865B00',
|
||||
A_actor='754499', A_other='326B80',
|
||||
B_ink='202020', B_secondary='494949', B_boundary='595959',
|
||||
B_accent='124E96', B_selection='E2ECFA').items(): C[name]=rgb(h)
|
||||
for prefix in ('A','B'):
|
||||
pairs += [(prefix+'_ink','white'), (prefix+'_secondary','BG'),
|
||||
(prefix+'_boundary','white'), (prefix+'_boundary','machine'),
|
||||
(prefix+'_accent','white'), (prefix+'_accent',prefix+'_selection'),
|
||||
(prefix+'_ink',prefix+'_selection')]
|
||||
pairs += [('A_amber','white'), ('A_amber','BG')]
|
||||
for name in ('A_actor','A_other','A_amber'):
|
||||
C[name+'_tint']=tuple(.94+.06*v for v in C[name])
|
||||
pairs.append((name,name+'_tint'))
|
||||
for name in ('keyword','identifier','string','number','A_secondary','error','punctuation'):
|
||||
pairs.append((name,'A_selection'))
|
||||
result=[]
|
||||
for a,b in pairs:
|
||||
ratio=contrast(C[a],C[b])
|
||||
result.append(dict(foreground=a, background=b, fg_hex=hexcode(C[a]),
|
||||
bg_hex=hexcode(C[b]), ratio=round(ratio,6),
|
||||
normal_text_4_5=ratio>=4.5, meaningful_graphic_3=ratio>=3))
|
||||
print(json.dumps(result,indent=2))
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Independent palette correction review
|
||||
|
||||
2026-09-13. Bounded source-only review for the parent's palette integration. No compiler, UI, Gitea, or production edits. Canvas, residual-consumer and read-only input correction reviews are complete below; parent owns compilation and runtime acceptance.
|
||||
|
||||
## Canvas contribution 092f524
|
||||
|
||||
Reviewed `092f5244b9264b53bccea884a1a4fe9d860f2ad2`, four-file patch on parent `aadc6396d39ff336d05381d58d545093d3f37a73`, together with foundation `02c2f6f46e57cf2cb00c2df789fbbad6caf973e7`'s palette roles. The canvas commit is not descended from foundation02: a whole-tree endpoint diff includes unrelated archives and reverses the foundation files. The actual four consumer files are identical between foundation02 and the canvas commit's parent (`git diff --quiet ... -- four paths` exited 0). This review therefore examines the contribution patch and its intended foundation combination, not an accidental whole-tree replacement. The isolated canvas commit requires foundation's BOUNDARY definition before compiling.
|
||||
|
||||
**No actionable introduced geometry, selection, or essential-contrast regression found in this bounded patch.**
|
||||
|
||||
- `behavior.rs:2077,2094` changes only unselected activation/card/frame color. It retains actual typed segment bounds and correlation, existing selected widths, guide/backplate/bar/message paint order, clipped label rendering, and exact-message/activation/label hit priorities.
|
||||
- `scenario_lifecycle/render.rs:243,245,265` changes guide/Alive-line color only. Guides remain 1-unit dotted `[2,5]`; Alive spans remain solid 2-unit strokes. Birth capsules, death strokes, clipping, unresolved `?`, legend, marker/guide hit targets, traversal and no-resurrection projection are untouched. Stronger guides increase density and need native inspection, but they do not fabricate lifetime intervals.
|
||||
- `diagram.rs:1176,1602` changes ordinary edge and unselected node boundaries only. Selected/trace/warning paths retain their branches; route and port points, label bounds, arrowheads, dashes, handles and hit functions are unchanged. BOUNDARY `#667586` is the foundation's stronger essential token; the actual existing white, activation and six-percent container tint surfaces are consistent with the reviewed palette inputs.
|
||||
- `state_canvas.rs:1517,1533,1568` retains the two selected row fills and adds an inset outline. A 1.5-unit stroke centered one unit inside the existing rectangle has its outer edge 0.25 units inside. StateAction rows are 26 high with text offset `(6,5)`; internal rows are 52 high with text at `(6,6)` and `(6,27)`. The added border does not replace or overlap the top separator at row.y, nor change the row-generating functions, identities, order or hit rectangles. The <=2 guard avoids negative outline dimensions without altering selection semantics. Existing text shortening is approximate and is not newly repaired by this patch.
|
||||
|
||||
The retained source-color tests are a numerical baseline, not proof of the new state-row pixels. Run existing state row identity/hit tests and actual selected-row paint/native witnesses after integration. Preserve the activation WGPU continuity and exact shoulder/message tests: a stronger color cannot replace their continuity property. At Fit/overview scales, 1-unit guides and narrow activation shoulders remain subject to rasterization and detail limits. This patch does not establish minimum-scale legibility or monitor acceptance.
|
||||
|
||||
The architecture warning cue remains explicitly deferred by the parent's scope and the canvas handoff. Warning routes still use the existing amber branch and text guidance. That non-hue discoverability gap is unfinished work, not a secretly completed requirement or a new defect introduced by these four paint changes. No unrelated warning-layout policy is recommended here.
|
||||
|
||||
Exact reviewed canvas payload identities:
|
||||
|
||||
| File under `crates/oa-desktop/src` | SHA-256 |
|
||||
| --- | --- |
|
||||
| `behavior.rs` | `f0b392e5a1e262fbc43901f1d0ffab49559b1bcbc72b7ac6e4d21672b0d55a55` |
|
||||
| `behavior/scenario_lifecycle/render.rs` | `07703c54a14de636122bdb36ee9b23d3115152d3c548bd09827a838632e5dc07` |
|
||||
| `behavior/state_canvas.rs` | `4332c96e2a10ff118fe238ddd5ab4410918abbaf738d6601402974ac3040c711` |
|
||||
| `diagram.rs` | `3ea54c682a4cb86880132a1b6aa603658fc0ba7abd9a7d18897876a585740e98` |
|
||||
|
||||
## Residual consumer audit at integrated bbd3b0e
|
||||
|
||||
Additional read-only audit requested by parent on `bbd3b0e84303fb6821bbcaceda641edc88b4a4d0`. These are existing consumers missed by the bounded foundation/canvas patches, not regressions falsely attributed to 092f. Ratios below use exact source colors and the same opaque sRGB arithmetic as the foundation review; no native rendering or test execution occurred.
|
||||
|
||||
**P2 — The active process-topology renderer still has faint essential node boundaries.** `behavior/topology.rs:1474` paints unselected node/control/source-use borders with LINE, 1-unit width, on `#F3F8F8`; `:1581` does the same for contribution-preview node rectangles filled white. Behavior routes Process/FunctionalChain owners to this renderer at `behavior.rs:1025–1030`, so fixing the separate Scene painter does not cover it. LINE/fill is **1.211763:1**, LINE/white **1.298757:1**, and the outer node fill/white is only **1.071792:1**. Node titles and routes have strong text/strokes but do not establish the containing node's bounds. Small correction: BOUNDARY at those two outline sites, retaining dimensions, ordered paint passes, direct/contextual edge distinctions, control glyphs and hits. Exercise an unselected process with a source-use contribution preview, not only a scenario.
|
||||
|
||||
**P2 — Custom reference-chooser and context-menu keyboard highlights bypass the improved selected styles.** `forms/reference_chooser/widget.rs:744–752` and `context_menu.rs:728–738` paint the actual highlighted/focused row solely with `#DAEEEB` on white, **1.206167:1**. Both selected and ordinary enabled text remain INK; neither path adds an outline/marker or consumes `style::selected`/Iced menu styles. Arrow keys change the chooser's highlighted candidate (`widget.rs:538–557`) and the menu's focused action (`context_menu.rs:213–227`), and Enter executes that row, making this an essential current-target cue. Small correction: add an inset TEAL outline or compact non-hue marker inside the existing highlighted row, preserving clipping, measured row bounds, keyboard state and disabled-row exclusion. Test the actual custom overlay paint, including keyboard-only movement and Enter; default menu style tests cannot cover these widgets. No selection-coordinate architecture is required.
|
||||
|
||||
**P2 — The custom context-menu scroll thumb is still a decorative divider color.** `context_menu.rs:785–795` paints its overflow thumb with LINE on white (**1.298757:1**). The thumb is actionable: `:248` starts the scroll-thumb drag path, and its position communicates list location. It is not a disabled widget or a separator. Small correction: use BOUNDARY or MUTED for the existing thumb fill only; retain the LINE separator at `:709` and current geometry/scroll state. Reproduce with an overflowing menu and verify the real thumb remains distinguishable before hover and during scrolling.
|
||||
|
||||
**P2 — The custom enabled reference-chooser trigger retains a faint field outline.** `forms/reference_chooser/trigger.rs:289–305` draws a white rounded control with ordinary LINE border, switching to TEAL only for hover/focus. The branch exists only when `on_open.is_some()`, so this finding does not reclassify a disabled control. It bypasses the now-strong default pick-list boundary. Small correction: use BOUNDARY for its ordinary enabled border, preserving the existing 1/2-unit normal/focus geometry and all event guards. Its associated popup boundary at `widget.rs:711` is also LINE with white backing and no shadow; using BOUNDARY there is a consistent bounded containment fix. Verify a chooser on the actual white Inspector panel, before hover and after keyboard opening.
|
||||
|
||||
Not flagged as additional defects: architecture port glyphs use strengthened kind colors/TEAL with white fills and 1.5/2.5 outlines; route pin handles use screen-scaled TEAL outlines; state pseudostates and route bodies use INK/TEAL; scenario lifecycle capsules/X, process flow arrows, reorder affordances and placement labels use strong roles. State header/internal-row separators, guidance separators, architecture grid dots, tree hierarchy/divider styling and ordinary panel/card decoration need not all become BOUNDARY. The port-label tooltip's LINE outline is faint, but its readable text/white backplate and transient identity presentation do not by themselves prove an essential-boundary failure; inspect it natively rather than escalating every remaining LINE use. Existing amber-warning discoverability remains the separately recorded requirement.
|
||||
|
||||
### Topology correction source review
|
||||
|
||||
Parent corrected the two topology outline sites in `2c6ededc516855b61fb4518240008ee36b60d305`. Reviewed its entire two-line substitution patch: both now use BOUNDARY; selected TEAL, widths, fills, preview arrow ordering, text, geometry and interaction code are unchanged. **No actionable defect found in this correction.** Topology payload SHA-256 after correction is `4bfc88eca8d4652dd361a33dd5f17927616545c0b5e12d2b8164dd3e1f9caff2`. Compilation and native process-preview acceptance remain the parent's separate gates. The other three residual custom-control findings above are not resolved by this topology-only correction.
|
||||
|
||||
## Read-only input correction 87133b9
|
||||
|
||||
Reviewed `87133b9d7b7fcd71dd7ea87485d0eed8d6edaaa2` against foundation02 in its isolated worktree; parent reports integration as `afa74a305980f611437dd3aa2f937d36e9266cfe`. Four changed files. **No actionable defect found in the bounded correction or the reviewed evaluator source.** Tests have not been compiled/executed by this reviewer; parent has started the style gate independently.
|
||||
|
||||
`style::selectable_input` starts with Iced's actual default style and overrides only `value = INK` when status is Disabled. It preserves active/hover/focus colors, the disabled white surface, border, placeholder and selection fill. Hosted service address, secure token and query inputs opt into it while retaining their original `on_input_maybe` guards. Secure rendering and existing submit handling are untouched. The selected value pair is now **4.684304944:1**; selection against the Disabled white surface is **3.467843126:1**. It does not alter the globally shared secondary-button/placeholder palette.
|
||||
|
||||
The style test replaces the incorrect unused-selection exemption with a negative control for the stock Disabled pair and a separate resolved adapter test. Ordinary non-highlighted editors receive their own resolved active-state coverage. The adapter test verifies preserved background/border/placeholder plus the operative selected-value pair; production code visibly leaves icon and selection unchanged too. No copied replacement style object pretends to be the runtime consumer.
|
||||
|
||||
`style/tests/readonly.rs` mounts an actual Iced text input with the same conditional-callback/style path. The fixture uses a real click, Command-A, Command-C and clipboard; it does not inject cursor or selection internals. One branch begins with no callback. The other selects while editable, sends Enter, checks the exact submit message, then rebuilds readonly with the same widget identity/value and actual Iced cache. Both verify copied Unicode text and that character insertion/Backspace produce no edit messages or changed copied text. The test is a small widget integration witness, not a complete Hosted network/busy-state test; the original application guards remain independently source-visible.
|
||||
|
||||
The rendered checks require the observed Disabled style, over 100 actual selection-color pixels, and dark value-glyph pixels inside the measured selection footprint. Old MUTED ink differs by far more than the 12-byte tolerance, so merely repainting the old failing glyph color cannot satisfy that glyph predicate. The selection footprint lies inside the text range and does not include the surrounding control border. `iced_runtime::UserInterface::draw` resets the renderer at `user_interface.rs:490` before each draw, preventing pixels from the preceding branch from satisfying the second branch. Specified-color ratios are checked separately from antialiasing. This is proportionate evidence for the actual reachability/paint issue; actual WGPU execution remains required.
|
||||
|
||||
This adapter does not introduce a new read-only icon or focused-state border for Disabled controls. Their interaction is read-only because callback absence remains intact; selected-range paint is now readable. The test does not claim all read-only/focus appearance or the secure-token copy restrictions are newly accepted. No source-editor syntax, model/source history, or canvas geometry path changes in this correction.
|
||||
|
||||
Exact reviewed correction payloads:
|
||||
|
||||
| File under `crates/oa-desktop/src` | SHA-256 |
|
||||
| --- | --- |
|
||||
| `style.rs` | `28b9527137d03850b617e3c248828e8111743e3b2d0ca8def31d8f381889ddd2` |
|
||||
| `style/tests.rs` | `b771d9656beafc1489ecef5e97d9b006ac385f77933930c42769cc35ca8ddffe` |
|
||||
| `style/tests/readonly.rs` | `6097ab8984ce61c90fc725c01c758391a30009004f0b1d42a4ebf4fd8b4106d9` |
|
||||
| `hosted/view.rs` | `862180b048e6ae28fa188aa889ab5b584061604c7cd7670ab83ec4dbf8c9ae91` |
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Independent review of palette foundation 02c2f6f
|
||||
|
||||
Reviewed contribution `02c2f6f46e57cf2cb00c2df789fbbad6caf973e7` against `f6595ebf66e88ec42c37b5c1a3c014f5e52cb32d`: four files, 369 insertions and 20 deletions. Read the complete selection remedy, readability audit, independent critique, source handoff, production consumers, and installed Iced 0.14.2 style/event/draw sources. This reviewer did not implement the candidate. No compiler, native UI, Gitea operation, or production edit was performed. Only source inspection and dependency-free color arithmetic were executed. Parent owns the separate canvas-consumer work.
|
||||
|
||||
## Finding requiring a bounded correction
|
||||
|
||||
**P2 — A reachable read-only input selection is excluded by a false test assumption and resolves to 2.068170:1.**
|
||||
|
||||
`crates/oa-desktop/src/style/tests.rs:158–159` says selection interaction is unavailable in Disabled text inputs and exempts their selected-value pair. Installed `iced_widget-0.14.2/src/text_input.rs` does not implement that assumption:
|
||||
|
||||
- Mouse press/word selection/triple-click at 720–826 and dragging at 838–904 set focus/selection without checking `on_input`. Keyboard copy and selection likewise remain available. Mutation branches separately require `on_input`.
|
||||
- `diff` at 656–662 stops pasting when disabled; it does not clear focus or selection.
|
||||
- Draw at 501–583 checks focus/window focus for selection. Only the caret branch uses `!is_disabled` at 515. The Range branch always paints `style.selection` at 540–571.
|
||||
- Status remains Disabled whenever `on_input` is absent (1364–1374), including while this selectable read-only input is focused.
|
||||
|
||||
This is a real application consumer: `hosted/view.rs:51–52,69–78,175–178` makes address, bearer-token and query inputs read-only using `on_input_maybe` while `app.busy`. A selected query can also persist when Enter submits the query and busy state changes. An initially read-only address can be selected directly. These source paths establish reachability; native observation is still pending.
|
||||
|
||||
Candidate `style.rs:46,49` supplies INPUT_SELECTION `#7B8E86` and secondary base MUTED `#4B5968`. Iced's default Disabled value becomes its active placeholder, i.e. MUTED (`text_input.rs:1789–1795`). It retains INPUT_SELECTION. Their opaque selected-text ratio is **2.068169555**, whereas ordinary INK on that same selection is **4.684304944**. The source editor's separate colors do not repair these ordinary inputs.
|
||||
|
||||
This is a candidate-state contrast gap, not a claim that the prior palette passed or a general WCAG rule forbidding inactive-control exemptions. Here the widget still offers a meaningful reading/selection/copy task, so claiming the pair is unused is factually wrong. Resolve that actual task rather than hiding it behind Disabled nomenclature.
|
||||
|
||||
**Small repair boundary:** correct the test's assumption and exercise the real read-only selected pair. A local ordinary-input adapter for the three existing conditional Hosted inputs can retain dark selected-value-capable ink while preserving `on_input_maybe(None)` and the read-only state; this avoids casually changing secondary base, which is also every default placeholder and secondary-button fill. Alternatively change a shared role only after reviewing its coupled consumers. Do not clear selection or disable copy merely to make the exempted state unreachable. Parent should choose the smallest coherent color/state distinction; this review makes no implementation change.
|
||||
|
||||
**Next executable falsifier:** resolve production `text_input::default(theme(), Disabled)` and assert selected value contrast, expecting the current source to fail near 2.06817. Add a real Iced widget-tree/render witness with no input callback, triple-click or drag a populated value, and verify the Range is painted, selected value remains readable, copying preserves text and typing publishes no mutation. A focused populated Hosted query becoming busy on Enter is the application transition witness. Keep the existing enabled-state checks. The exact widget test and native tasks have not been executed here.
|
||||
|
||||
## Other conclusions and limited coverage gaps
|
||||
|
||||
The central Extended-pair approach is source-compatible with the actual public Iced APIs. Explicit Pair literals deliberately bypass Iced's text adjustment, and the tests mostly call resolved production styles rather than merely validating copied palette pairs. Primary/secondary/success/warning/danger button hover states retain base text on strong fill, and checked primary checkbox hover does the same; the candidate's white text and stronger fills cover those actual cross-state combinations. Pick-list/menu backgrounds, borders, placeholder colors and selected-menu foregrounds match the installed implementations. No additional actionable active-state pair failure was found in those reviewed consumers.
|
||||
|
||||
Default ordinary `text_editor` consumers also exist outside the highlighted Source page: descriptions/conditions, semantic-review text, and Hosted read-only source. Their active/focused colors resolve to the ordinary input BG/INK/INPUT_SELECTION combination and therefore have the same passing source arithmetic. They are not all source-styled, which is appropriate. However, the new suite tests ordinary `text_input` and custom Source `text_editor`, not default ordinary `text_editor`; adding that resolved-style case is useful protection against future divergence in the two Iced defaults. This is a coverage improvement, not a demonstrated present failure.
|
||||
|
||||
Default scrollbars are another actual shared-role consumer (`scrollable.rs:2345–2429`, many panes in `views.rs`). Ordinary thumb MUTED on white rail is **7.172088:1**; hover uses primary strong and drag uses TEAL. No present contrast issue was found. The suite omits them. If the foundation is described as covering all default controls, add their resolved states or scope that claim. Auto-scroll overlay alpha/shadow remains a separate composite/native visual check.
|
||||
|
||||
The canvas-surface test copies five painter fill constants and the six-percent tint expression. It tests actual INK/MUTED/BOUNDARY/kind outputs, but duplicated fills can become stale if a painter changes. Treat it as a palette baseline, not proof that every actual Scene/canvas paint pair is covered. The separate canvas-consumer work and actual paint witness should verify the real consumer surface/foreground pair. A production palette abstraction solely to avoid a handful of test literals is unnecessary without broader need.
|
||||
|
||||
Unused stock `button::background`/`subtle` and non-primary checkbox styles have further pair couplings (for example pressed background buttons retain base text over background.strong). Search found no application consumers of those styles. This review does not require exhaustive unused-Iced-style support or count those hypothetical combinations as current application defects.
|
||||
|
||||
## Syntax, selection lifecycle, disabled state and focus
|
||||
|
||||
The diff changes only formatter colors, leaving lexer classes, byte spans, multiline splitting, highlighter settings, source snapshot identity, font mapping, and source edit/persistence/history machinery unchanged. The sole Source `.style` adapter preserves inherited status-dependent border geometry while assigning its white surface, dark fallback/placeholder/caret and separate selection fill. It does not track selection, introduce range-state caches, recolor only selected text, or trigger selection-specific tokenization. No newly introduced stale-highlight or source mutation path was found.
|
||||
|
||||
The S2 colors are very dark and closely matched in luminance; readable ordinary/selected glyph arithmetic does not prove token categories remain easy to scan. Native source reading/editing at the actual font/DPI remains necessary, particularly comments, punctuation, number/string distinctions, error diagnostics and multiline selections. Existing empty/newline-only selection geometry and blur-hidden range paint remain unchanged and must not be claimed repaired.
|
||||
|
||||
The Source adapter makes Active and Disabled background/value/placeholder colors identical; its current production Source call always supplies `on_action`, so Disabled is not a newly reachable ordinary application state there. The test covers Disabled colors but does not prove a distinct read-only cue. Record that limitation if this adapter is reused later; no speculative production change is required now.
|
||||
|
||||
Selected navigation correctly preserves inherited disabled text alpha instead of restoring opaque INK. Its 2-unit outline provides a non-fill selection cue. It is not a keyboard-focus implementation. Default source/input focus still changes a one-unit border's color (ordinary BOUNDARY to primary strong), with no extra geometry; it is visible against the surface, but source ratios alone do not prove the focused field can always be located on the user's display. This behavior is inherited, explicitly scoped in the handoff, and not a new blocker to the palette experiment. Native focus transfer should remain a separate acceptance observation. Disabled button alpha tests correctly avoid pretending an opaque contrast equation establishes composited appearance.
|
||||
|
||||
## Identity and handoff
|
||||
|
||||
Local candidate payload hashes match the source handoff:
|
||||
|
||||
| File | SHA-256 |
|
||||
| --- | --- |
|
||||
| `style.rs` | `924e6a92bc28c9f07936c8ab0bc41b6f097956ef4c0e24a571d7fa6fb6591382` |
|
||||
| `style/tests.rs` | `4a3f7486bfa85129d1504f7a0990e28325bd704770f814f255d5044996246dcd` |
|
||||
| `highlight.rs` | `8222e2deed5d60ea522241ccb2c653caf50ecc1d4404d633f54e0193c5f622e9` |
|
||||
| `views.rs` | `1c0b1197d3bf3d3f7aaaea2cf8a89bf4f7e2634e65345f81693fc91df151b9c3` |
|
||||
| installed Iced `text_input.rs` | `7cd64b0f79c6000f2985cc290642698a814be5dcd7b7a56ae463b13ffe5c3b47` |
|
||||
| installed Iced `text_editor.rs` | `cecf8686c90aa92066d55075beba545dfb7dd5a44bad0c1519fcb110a56070e1` |
|
||||
|
||||
Color arithmetic used the final byte colors, sRGB decoding at 0.04045, luminance weights 0.2126/0.7152/0.0722 and the unrounded luminance contrast ratio. It did not model antialiasing, alpha compositing, gamut, monitor calibration or run the Rust test suite. Recommendation: address the reachable read-only input selection and retain the bounded candidate for combined gates/native evaluation. No palette/display acceptance or issue closure is established by this review.
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
# Palette foundation source handoff
|
||||
|
||||
2026-09-12. Root owns issue #26, integration, compiler gates and native acceptance. This is an isolated candidate implementation, not issue closure or an accepted display result.
|
||||
|
||||
- Branch: `codex/palette-foundation-956atf-20260912`
|
||||
- Worktree: `.local/palette-foundation-20260912`
|
||||
- Base: `f6595ebf66e88ec42c37b5c1a3c014f5e52cb32d`
|
||||
- Contribution: `02c2f6f46e57cf2cb00c2df789fbbad6caf973e7` — Strengthen palette roles and source selection contrast
|
||||
- Scope: exactly four files below. No behavior, architecture/state painter, lifecycle, lexer, source action/persistence, manifest, dependency or root tracked changes.
|
||||
|
||||
## Implemented candidate
|
||||
|
||||
`style::theme()` uses the installed `Theme::custom_with_fn` API and explicit shared Extended pairs. Existing application/input BG is preserved. Semantic secondary ink is `#4B5968`; teal is `#00665C`. Essential `pub const BOUNDARY` is `#667586`, distinct from unchanged decorative LINE. It already supplies input/pick-list/menu/checkbox boundaries through the theme and is ready for the independently owned canvas-consumer replacements.
|
||||
|
||||
This is **A refined**, not original A unchanged: parent chose a slightly darker global ink to make ordinary selection both visible and readable without changing the surface. Final INK `#18212C` and ordinary input selection `#7B8E86` have approximately 4.684305 value/selection, 3.137133 selection/BG and 14.695286 value/BG contrast. Comparison `#1C2530` / `#7C9188` yielded only 4.616181 and 3.033414 for the first two measures. These are source-color arithmetic; actual f32-resolved Rust tests remain unrun.
|
||||
|
||||
The actor, generic and amber kind-color families darken to A's proposed values. Existing category mapping and the other kind families remain. Shared teal darkens activity/function colors naturally. No category semantics, text, shapes or lexer classes change.
|
||||
|
||||
Selected navigation retains a light `#D9EEE8` fill and gains a 2 px teal outline, using the existing style API. It no longer overwrites disabled text opacity inherited from the base navigation style. This is an outline, not a new leading marker or focus-management implementation.
|
||||
|
||||
The single source editor call gains `.style(style::source_editor)`. Its white surface, `#80958B` selection and `#212529` fallback/placeholder/caret are separate from ordinary input values. `highlight::format` uses the exact seven S2 colors from the [selection remedy](palette-selection-remedy-20260912.md), with unchanged font and lexer behavior. S2 source arithmetic gives 3.186824 selection/surface and at least 4.835525 selected-token/fallback contrast. No range tracking, additional retokenization or selected-foreground lifecycle is added.
|
||||
|
||||
## Shared-role couplings and implementation choice
|
||||
|
||||
Iced `Pair::new` silently adjusts text using `Color::is_readable_on`'s **6.0** threshold. Therefore the generator uses a local Pair-literal constructor to preserve reviewed values exactly; resolved tests enforce the intended consumer contrasts. It does not rely on Iced's adaptive color output or introduce a theme abstraction.
|
||||
|
||||
- `background.weak` supplies white pick-list/menu/hovered unchecked-checkbox and disabled-input surfaces. It is not darkened as a border token.
|
||||
- `background.strong` supplies BOUNDARY borders and the disabled checked primary checkbox fill. Its own text pair is white; some checkbox variants use other pair text, so pair-local checks alone would not suffice.
|
||||
- `secondary.base` supplies both MUTED placeholders and secondary-button fill. Its text is white, with a darker explicit hover fill.
|
||||
- Primary base/strong supply teal actions, focused/open borders, checked boxes and menu selection. Hovered buttons and checkboxes can retain base text while consuming strong fill; the tests check resolved output.
|
||||
- `primary.weak.color` supplies ordinary input selection. The source editor overrides selection/value/surface locally rather than forcing S2 onto ordinary fields.
|
||||
- Shared success/warning/danger pairs are explicit and coherent with the light theme. Existing disabled-button alpha behavior is retained and is not evaluated as if it were opaque active text.
|
||||
|
||||
## Verification performed and deferred
|
||||
|
||||
Performed in the isolated worktree: source/API inspection, `rustfmt --edition 2024 --config skip_children=true` on the four owned Rust files (exit 0), and `git diff --check` (exit 0). The view diff is exactly one style call. Git commit succeeded and the worktree status was clean immediately afterward. No Cargo, rustc, native UI or Gitea operation was performed.
|
||||
|
||||
Seven tests are **declared, not run** in `style/tests.rs`:
|
||||
|
||||
1. `semantic_text_and_essential_boundaries_have_contrast_on_canvas_surfaces`
|
||||
2. `source_editor_syntax_and_fallback_remain_readable_during_selection`
|
||||
3. `default_input_styles_keep_ordinary_values_and_visible_selection`
|
||||
4. `default_pick_list_and_menu_styles_cover_open_and_selected_states`
|
||||
5. `default_checkbox_styles_keep_checks_and_outlines_distinct`
|
||||
6. `button_pair_couplings_cover_hover_and_disabled_states`
|
||||
7. `selected_navigation_adds_a_visible_outline_and_preserves_disabled_ink`
|
||||
|
||||
They use actual theme, formatter and Iced default style functions, not a copied hypothetical palette. They cover all 39 kind headers on the painter's tint, relevant diagram surfaces, every source class, enabled input statuses, disabled input values, opened pick lists/selected popup rows, checked/unchecked primary boxes, five filled button families across active/hover/pressed, and disabled/nav behavior. Opaque contrast helpers reject alpha/gradients rather than mismeasure them. Disabled button alpha and inactive unchecked borders are explicitly exempt from active-state contrast floors; this does not claim their perceptual quality is verified. Non-default success/secondary/danger checkbox variants are not used by current production call sites and are not claimed covered by the primary-checkbox evaluator.
|
||||
|
||||
After the parent combines the independently owned painter contribution and grants a compiler lease, a targeted command is:
|
||||
|
||||
```powershell
|
||||
cargo test --locked -p oa-desktop --bin arcadia-studio style::tests -- --nocapture
|
||||
```
|
||||
|
||||
Use the parent's identified-input Windows gate environment and isolated/serialized target policy. Require a listing selecting all seven tests; compilation alone or zero selected tests is not evidence. The normal integrated full/strict and native gates remain necessary. No compile success is claimed here.
|
||||
|
||||
## Remaining acceptance and geometry boundaries
|
||||
|
||||
- Actual candidate rendering, syntax hue scanability, display degradation and native editing tasks are unverified. Near-black syntax is an explicit tradeoff, with improved foreground/range contrast but less saturated scanning information.
|
||||
- Essential canvas LINE/edge consumers remain for the parent's separate contribution; exposing BOUNDARY alone does not change those still-hardcoded outlines. Shared token consumers naturally receive changed INK/MUTED/TEAL values.
|
||||
- Warning-route symbols, state-row selection markers and any separate focus geometry are not implemented in these four files. A selected nav outline does not imply keyboard-focused buttons receive a new focus indicator.
|
||||
- Iced can produce no selection rectangles for newline-only selection and omit blank-line fill; range paint disappears on blur. These source-derived geometry limits remain unchanged. The native acceptance matrix in the selection remedy includes them and must record actual behavior rather than silently narrowing universal selection visibility.
|
||||
- Existing painter-continuity screenshot predicates may depend on old selected colors. Update only the color recognition when combining, while retaining the geometric continuity requirement.
|
||||
|
||||
## Exact payload hashes
|
||||
|
||||
| File under the worktree | SHA-256 |
|
||||
|---|---|
|
||||
| `crates/oa-desktop/src/style.rs` | `924e6a92bc28c9f07936c8ab0bc41b6f097956ef4c0e24a571d7fa6fb6591382` |
|
||||
| `crates/oa-desktop/src/style/tests.rs` | `4a3f7486bfa85129d1504f7a0990e28325bd704770f814f255d5044996246dcd` |
|
||||
| `crates/oa-desktop/src/highlight.rs` | `8222e2deed5d60ea522241ccb2c653caf50ecc1d4404d633f54e0193c5f622e9` |
|
||||
| `crates/oa-desktop/src/views.rs` | `1c0b1197d3bf3d3f7aaaea2cf8a89bf4f7e2634e65345f81693fc91df151b9c3` |
|
||||
|
||||
Parent owns independent review, combination, execution, evidence archive and any eventual acceptance/publication.
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
# Palette integration decision and acceptance boundary
|
||||
|
||||
Root Codex-956atf, issue #26, 2026-09-13 UTC. The user requires graphical elements
|
||||
to remain clear on monitors with limited color reproduction. The first bounded
|
||||
candidate strengthens luminance contrast while preserving existing category
|
||||
words, shapes, dashes and colors. Neutralizing every category was rejected for
|
||||
this trial because it removes a useful grouping cue without evidence that this
|
||||
is necessary. Increasing every decorative divider was rejected because it adds
|
||||
clutter without strengthening essential geometry selectively.
|
||||
|
||||
Foundation 02c2f6f and canvas 092f524 are integrated as 8c3e7ac and bbd3b0e after
|
||||
lifecycle integration aadc639. The candidate uses INK #18212C, MUTED #4B5968,
|
||||
TEAL #00665C and essential BOUNDARY #667586. Decorative LINE remains separate.
|
||||
Selected navigation and state rows have explicit outlines. Canvas geometry,
|
||||
lifetimes, message correlation, route paths and hit targets are unchanged.
|
||||
|
||||
The Source editor uses candidate S2: a white surface, #80958B selection and dark
|
||||
syntax colors. Its static palette avoids duplicating the editor's Unicode
|
||||
word/line selection rules. Ordinary controls retain their separate selection
|
||||
pair. Explicit resolved Iced pairs avoid generated colors silently invalidating
|
||||
the measured candidate. The tradeoff is reduced visual separation among syntax
|
||||
colors; native reading and selection tasks must assess it.
|
||||
|
||||
Engineering benchmarks are 4.5:1 for semantic text and 3:1 for essential graphics
|
||||
against their actual adjacent surfaces. These are not a claim of universal native
|
||||
accessibility conformance or physical monitor performance. Alpha-composited
|
||||
disabled controls, antialiasing, thin strokes and tiny overview labels need
|
||||
separate interpretation. Stronger source colors cannot repair missing strokes.
|
||||
|
||||
Fresh review found that Iced inputs without editing callbacks still select and
|
||||
copy. Their Disabled text used MUTED over ordinary selection, only 2.068:1.
|
||||
A narrow correction must preserve this useful interaction and test actual
|
||||
selection/copy behavior before the palette can enter the combined gate.
|
||||
|
||||
The proposed searchable routing-warning selector is a separate interaction
|
||||
change. Inspection found no existing host access to the actual routed scene or
|
||||
private held gestures, and the current menu has no search. Adding a new bridge
|
||||
and search while promising draft/gesture preservation requires explicit source
|
||||
and event-order contracts. Its implementation is deferred; existing warning
|
||||
text remains, and #26 is not closed by the color contribution.
|
||||
|
||||
Acceptance requires resolved production-style tests, the existing activation
|
||||
pixel/identity and lifecycle tests, combined desktop/strict/GUI gates and a
|
||||
native trial of architecture routes, state selection, lifetimes and source/input
|
||||
selection at normal and minimum-window Fit scales. Preserve exact Project and
|
||||
SourceBundle through Save/reopen. Report overview and keyboard-focus limitations
|
||||
when observed. No palette or monitor acceptance has yet been established.
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
# 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.
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"kind": "source-only independent arithmetic; no rendering or application tests",
|
||||
"stored_json_exact_match": true,
|
||||
"row_count": 63,
|
||||
"extra_pairs": [
|
||||
{
|
||||
"foreground": "A_selection",
|
||||
"background": "BG",
|
||||
"ratio": 1.095238033137858
|
||||
},
|
||||
{
|
||||
"foreground": "B_selection",
|
||||
"background": "BG",
|
||||
"ratio": 1.0786368739536105
|
||||
},
|
||||
{
|
||||
"foreground": "comment",
|
||||
"background": "A_selection",
|
||||
"ratio": 3.6562507523497163
|
||||
},
|
||||
{
|
||||
"foreground": "comment",
|
||||
"background": "B_selection",
|
||||
"ratio": 3.712523629926024
|
||||
},
|
||||
{
|
||||
"foreground": "number",
|
||||
"background": "A_selection",
|
||||
"ratio": 4.731669041796004
|
||||
},
|
||||
{
|
||||
"foreground": "A_boundary",
|
||||
"background": "A_accent",
|
||||
"ratio": 1.4578026659350451
|
||||
}
|
||||
],
|
||||
"number_requires_selection_luminance_at_least_for_4_5": 0.7748070771768231,
|
||||
"selection_fill_requires_luminance_at_most_for_3_against_BG": 0.2666222910006454,
|
||||
"source_files": [
|
||||
{
|
||||
"path": ".local/palette-readability-audit-20260912.md",
|
||||
"sha256": "1039a31206f7fa702b4eae310c83268c3c7a063c55a38d8aab2dc3c248b20d1a"
|
||||
},
|
||||
{
|
||||
"path": ".local/palette-contrast-20260912.py",
|
||||
"sha256": "f81d4b47cb15998adb33a3115b4e19fad96385238371860de771bb9b46baeb36"
|
||||
},
|
||||
{
|
||||
"path": ".local/palette-contrast-20260912.json",
|
||||
"sha256": "0b751a07823a7259d8a549e72febdb2fe2084211eb9cd0504cdbd93d173df8dd"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/style.rs",
|
||||
"sha256": "05d61bec16141612ccafa0988e8c4827f41754509efdb3b961eab69127ded896"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/highlight.rs",
|
||||
"sha256": "da7c254fff7052e5fd9ec7469f473b91a819406028d7c304f6e22c4169561600"
|
||||
},
|
||||
{
|
||||
"path": "crates/oa-desktop/src/views.rs",
|
||||
"sha256": "9b53126d19b2ae34f170a1422c9f5ea5a833489274cbfa04546279058e289f9b"
|
||||
}
|
||||
]
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# Independent palette decision critique
|
||||
|
||||
2026-09-12; scope owned by root under issue #26. Reviewed the audit attributed to production `d61f1f38779ab70a77efcde523ae31c432aefcfd`, the actual style/highlighter/editor construction, and installed Iced 0.14 sources. No production change, compiler, application test, native UI action, or Gitea mutation. The only execution was dependency-free source-color arithmetic. [Calculation receipt](palette-readability-independent-calculation-20260912.json) records exact input SHA-256 values and additional ratios. This review is independent of the palette author.
|
||||
|
||||
## Decision
|
||||
|
||||
Proceed with A's **role separation and preservation of category cues as a bounded experiment**, not acceptance of its exact colors or a resolution of the monitor requirement. The audit correctly identifies substantial source-contrast defects and keeps thin geometry and the separate painter defect in scope. Its arithmetic is sound. However, the source-editor selection remedy is not yet concrete, and the A/B comparison changes contrast strength and category semantics together. B is not evidence that neutralizing categories is necessary.
|
||||
|
||||
The requirement remains clear, sharp, usable graphical editing on limited-color displays, alongside the readable highlighted source editing required by `PROJECT_GOAL.md`. Contrast arithmetic helps establish a useful baseline; it cannot establish display-specific sharpness or native usability by itself.
|
||||
|
||||
## Findings requiring resolution before acceptance
|
||||
|
||||
### P1 — The proposed source selection remedy cannot be supplied by an ordinary style adapter
|
||||
|
||||
Candidate A's selected syntax ratios pass only after darkening comments, but its selection fill differs from the actual editor background by **1.095238:1**. B is still closer at **1.078637:1**. The audit's accent/selection ratio describes a possible added marker, not a marker the source editor currently renders. Neither ratio proves a selected range is identifiable on the problem display.
|
||||
|
||||
In installed `iced_widget-0.14.2/src/text_editor.rs`, `Style` at 1423–1435 offers background, border, placeholder, value and selection colors. There is no selected-text color or range-outline field. The draw code's `Selection::Range` branch at 1049 draws plain selection quads; the separate `Selection::Caret` branch does not also run for a range. Changing the editor's outer border identifies focus, not the selected characters. `highlight::format` at `crates/oa-desktop/src/highlight.rs:62` receives token class and theme, not the selection range. Its explicit token colors also mean changing the widget's fallback value color does not recolor all selected syntax.
|
||||
|
||||
Consequently, “lighter selection fill with a clear independent boundary/caret or deliberately adapt selected text” is a valid direction but **not an implementation already covered by the small style adapter**. Do not represent a light-fill-only adapter as resolving this finding. This is a blocker to accepting the proposed selection solution, not evidence of a newly witnessed native defect or a reason to block independent boundary/text fixes.
|
||||
|
||||
There is a useful numerical constraint. With the current number token `#A34E1E`, a lighter selection background needs relative luminance at least **0.774807** to retain 4.5:1 text contrast. A darker fill that alone reaches 3:1 against editor BG needs luminance at most **0.266622**. Those conditions are incompatible. This conditional engineering target illustrates why simply searching for another light tint is insufficient; it is not a claim that WCAG universally requires a 3:1 change between background states.
|
||||
|
||||
**Evaluator / bounded next choice:** first render a real highlighted multi-line selection with A's explicit fill and darkened comment, including short selections, blank spans, non-ASCII text, keyboard extension and copy. Require the selected range and its endpoints to remain identifiable, not merely legible text. If stronger independent range geometry is needed, identify the actual widget extension/renderer hook and test clipping/scrolling before accepting its cost; alternatively evaluate a selected-foreground implementation. Do not silently disable syntax highlighting or globally darken selection while keeping the incompatible token colors.
|
||||
|
||||
### P2 — B changes two important variables, so it does not isolate the reason for an improvement
|
||||
|
||||
A and B differ in luminance margin as well as chromatic/category policy: boundary/white is 4.72 versus 7.00; secondary/BG is 6.49 versus 8.14. B also removes category hue from ordinary headers/edges. If B succeeds where A fails, that result cannot distinguish stronger contrast from the effect of neutral category rendering. A can use a darker neutral boundary or secondary token while retaining semantic swatches.
|
||||
|
||||
There is no source or measured hardware evidence here that a blue interaction accent survives the user's display better than teal. Both palettes still need non-hue selection and warning information. Preserving existing category words/shapes is sound; keeping or removing hue is a cognitive/visual-grouping tradeoff that remains to be evaluated.
|
||||
|
||||
**Evaluator:** use the same fixture, stroke geometry, DPI and interaction task. Compare A against a contrast-strengthened A retaining category cues, then compare neutralized categories at matched luminance if ambiguity remains. Measure locating a requested type, following an unselected route, finding current selection/focus and identifying a warning. This avoids attributing a darker-line improvement to removal of category information. It does not require a permanent theme switcher or more production modes.
|
||||
|
||||
### P2 — The comment substitution and B's selected syntax are incomplete acceptance inputs
|
||||
|
||||
The supplied calculator substitutes `A_secondary` for the comment token in candidate A's selected-text checks. That is an explicit proposed change, not current highlighter behavior. The unchanged comment is **3.656251:1** on A selection and **3.712524:1** on B selection. `highlight::format` currently ignores Theme, so changing global text tokens alone leaves both failures. A's tightest supplied selected-token margin is the number token at **4.731669:1**; use actual resolved values rather than assuming all selected syntax has the much larger body-text margin.
|
||||
|
||||
B's selected syntax cross-product is absent from the supplied calculator. If B preserves existing syntax colors, it also needs comment darkening; if it neutralizes syntax, that is a separate, broader source-editing decision than neutral category headers.
|
||||
|
||||
**Evaluator:** check every actual formatter output on the resolved normal and selection backgrounds, with a direct assertion for the intended comment mapping. Cover ordinary text inputs independently; their selected foreground behavior must not be assumed identical to the highlighted editor. Keep the old comment and generated-selection failures as negative controls.
|
||||
|
||||
## Verified arithmetic and rendering interpretation
|
||||
|
||||
The calculator's **63 output rows exactly match the saved JSON** when rerun. Its sRGB decoding, relative-luminance weights and contrast equation are consistent with the stated benchmark. Retaining float source constants avoids false passes caused by rounded hex labels. Its six-percent container tint uses the same encoded-channel affine expression as `diagram.rs:1591`; it correctly does not substitute linear-light blending for that source expression.
|
||||
|
||||
The generated editor selection calculation also matches Iced's implementation: `iced_core-0.14.0/src/theme/palette.rs:506` derives primary weak using `mix(base, background, 0.4)`, and `mix` at 675 blends linear channels. A generated `Pair` has a readable `.text`, but the editor consumes only primary weak's `.color` for selection. The existence of `.weak.text` therefore does not repair selected syntax automatically.
|
||||
|
||||
The widget calls `fill_editor` before queuing the selection rectangles. That order alone must **not** be interpreted as opaque rectangles covering the text. In the actual WGPU backend, `iced_wgpu-0.14.0/src/layer.rs:41,86` queues quads and editor text separately; `src/lib.rs:485,645` renders quads before text in the layer. The selection fill is behind the glyphs in this path. This makes the source-color pairing credible, while leaving actual rendered/native selection usability unverified.
|
||||
|
||||
Control backgrounds need per-widget inspection. In particular, default pick-list background is `background.weak.color`, not editor BG; generated backgrounds and borders use Oklch-derived values. A ratio against the convenient BG constant cannot be reused for every generated control. The audit correctly calls for resolved style tests rather than guessed theme colors.
|
||||
|
||||
## Benchmark and alpha cautions
|
||||
|
||||
The audit mostly uses the standards carefully. Keep essential node/activation outlines separate from optional panel dividers. Not every control border is required to identify its control, and WCAG does not generally require 3:1 between nonadjacent normal/hover/selected state colors. A necessary state indicator should be assessed against its actual adjacent surface and geometry. A fill with low contrast can still participate in an adequate compound cue. [W3C non-text contrast explanation](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html)
|
||||
|
||||
Likewise, the specified color contrast benchmark and the appearance of antialiased thin strokes are different checks. The standards guidance warns that nominally passing thin lines can look faint; sampled edge pixels should not silently replace the specified-color test. Preserve both numerical checks and a separate native traceability/readability task. [W3C non-text contrast explanation](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html), [W3C text contrast explanation](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html)
|
||||
|
||||
The calculator is an opaque-pair calculator, not a compositor. Iced's linear-light theme mixing is verified above; it does not establish the blending path of every alpha-bearing UI primitive. Scrims, shadows, disabled alpha and glyph coverage need their actual backing surface and renderer path. Do not apply a single generic encoded-channel alpha formula to all of them. The audit already notes that enabled custom navigation restores INK, so Iced's default text-button hover alpha should not be counted as an additional navigation defect.
|
||||
|
||||
For a luminance-preserving grayscale falsifier, decode sRGB, compute relative luminance, then encode that value into equal RGB channels. A generic image-library grayscale operation on encoded channels need not preserve these contrast ratios. Channel quantization and veiling/compressed luminance tests expose different failure mechanisms; document parameters and label them stress tests, not calibrated simulations of the user's monitor.
|
||||
|
||||
## Scope and acceptance recommendation
|
||||
|
||||
1. Implement the minimal semantic role split and explicit generated-control styles, with comment darkening and an explicit source-selection decision. Keep decorative lines lighter; avoid globally replacing LINE and increasing visual density everywhere.
|
||||
2. Verify resolved production styles and highlighted outputs, then inspect real WGPU witnesses of the same architecture/state/scenario fixtures and source/Inspector editing. Distinguish selected object, keyboard focus, hover and warning without relying solely on hue. A boundary-to-accent color change alone has only 1.46:1 in candidate A; rely on the intended geometry/marker as well, without inventing a universal between-state contrast rule.
|
||||
3. Retain the audit's minimum-window/Fit and lowest-zoom checks, recording actual DPI. If nominal colors pass but thin boundaries remain untraceable, evaluate screen-space stroke treatment or detail presentation separately. Do not close the monitor issue because token ratios pass, and do not promise arbitrary tiny text can become readable through palette changes.
|
||||
4. Keep the existing painter-continuity checks independent of color identity; update a hardcoded color predicate only against the identified changed source and reverify the actual continuity property. Missing painted segments are not repaired by stronger palette values.
|
||||
|
||||
No palette has been rendered or accepted in this review. A remains the lower-migration-cost experiment; the selected-range implementation and rendered task evidence remain concrete acceptance dependencies.
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Read-only input selection correction: source handoff
|
||||
|
||||
Correction to the [palette foundation handoff](palette-foundation-source-handoff-20260912.md), prompted by the P2 finding in the [independent review](palette-foundation-independent-review-20260913.md). Root owns issue #26, integration and execution. No compiler, native UI, Gitea or root tracked edit was performed by this worker.
|
||||
|
||||
- Worktree: `.local/palette-foundation-20260912`
|
||||
- Branch: `codex/palette-foundation-956atf-20260912`
|
||||
- Preserved parent commit: `02c2f6f46e57cf2cb00c2df789fbbad6caf973e7`
|
||||
- Separate correction: `87133b9d7b7fcd71dd7ea87485d0eed8d6edaaa2`
|
||||
- Status immediately after commit: clean.
|
||||
|
||||
## Confirmed problem and correction
|
||||
|
||||
Installed Iced text_input mouse/keyboard handlers allow selection and copying without `on_input`. Its draw Range branch paints selection even in Disabled status; only the caret branch suppresses disabled caret painting. Hosted address, bearer-token and query fields actually use `on_input_maybe(None)` while busy. The earlier test comment claiming disabled selection was unavailable was wrong. The stock Disabled value is MUTED, whose selected pair with INPUT_SELECTION is approximately **2.068170:1**.
|
||||
|
||||
New `style::selectable_input` starts from the actual Iced default style and changes only `value` to INK when Status is Disabled. The three Hosted conditional input constructions now use it. Existing backgrounds, borders, placeholders, selection fill, callbacks, secure masking, submit handling and lifecycle remain unchanged. In particular, the bearer token's existing secure-input copy restriction is not relaxed.
|
||||
|
||||
This restores approximately **4.684305:1** for value/selection while retaining the existing distinct read-only surface. It does not change the shared secondary role or secondary-button colors. Enabled conditional-input styles retain the default value INK and existing surfaces. The source-editor S2 style/formatter is untouched.
|
||||
|
||||
The original seven-test handoff's disabled-selection exemption is superseded by this correction. Its historical source remains preserved in 02c2f6f; it must not be used as an acceptance argument.
|
||||
|
||||
## Test source and verification status
|
||||
|
||||
Performed: actual Iced source inspection, source formatting with `rustfmt --edition 2024 --config skip_children=true`, and `git diff --check`; both commands exited 0. Git commit succeeded. **Compilation and every Rust test remain unrun.**
|
||||
|
||||
There are now **10 declared style tests**, including these three additions:
|
||||
|
||||
- `conditional_input_styles_preserve_readonly_selected_value_contrast`: resolves the production adapter for Active/Hovered/Focused/Disabled; checks normal and selected text plus visible range; verifies that surface/border/placeholder are inherited.
|
||||
- `ordinary_editable_editor_styles_keep_their_own_selected_value_readable`: covers the actual default editor Active/Hovered/Focused outputs independently of S2. It does not assert a contract for unused no-action editor states.
|
||||
- `readonly::mounted_readonly_input_keeps_selection_copy_and_readable_paint`: mounts the real Iced text input with the same conditional-callback/style path as Hosted, using the production WGPU headless renderer. It sends mouse focus and keyboard Select All, exercises an initially read-only field and an enabled field submitted then rebuilt read-only using the same widget identity and UI cache, copies the actual selected Unicode value, attempts typing/backspace without a callback, and copies again to verify value/selection preservation. It requires no mutation messages, observes actual Disabled style resolution, and checks real selection-fill and dark-glyph pixels within the rendered range. Specified-color contrast checks are kept separate from antialiased pixel identification.
|
||||
|
||||
The prior default-input test now retains the stock Disabled selected pair as an explicit **negative control**, not an exemption. The regression test invokes the production adapter and real widget; it does not manufacture private focus/cursor state, disable selection, or clear the selection to evade the failure. It is a widget-level Hosted-pattern fixture, not an App/network submission test; the actual Enter-to-busy Hosted workflow remains in native acceptance.
|
||||
|
||||
After integration and serialized compiler authorization, the targeted command remains:
|
||||
|
||||
```powershell
|
||||
cargo test --locked -p oa-desktop --bin arcadia-studio style::tests -- --nocapture
|
||||
```
|
||||
|
||||
Require an exact listing/count of ten selected tests and successful completion. The WGPU test needs the same supported headless environment as existing mounted/render tests. No execution result is inferred from source review or formatting.
|
||||
|
||||
## Scope and remaining evidence
|
||||
|
||||
Only four files change in this correction. No global secondary-pair change, lexer/editor lifecycle change, canvas edit or dependency change. Default scrollbar coverage was not expanded; the independent review's already-passing arithmetic remains scoped rather than claiming exhaustive widget coverage. Native focus, syntax scanability, physical-monitor behavior, empty/newline-only source selection geometry and other foundation limitations remain as previously documented.
|
||||
|
||||
Parent was given the exact correction identity to wake `nested_call_design` for independent source review before integrated gates. That review and all executable results are pending at this handoff.
|
||||
|
||||
| File under the worktree | SHA-256 |
|
||||
|---|---|
|
||||
| `crates/oa-desktop/src/style.rs` | `28b9527137d03850b617e3c248828e8111743e3b2d0ca8def31d8f381889ddd2` |
|
||||
| `crates/oa-desktop/src/style/tests.rs` | `b771d9656beafc1489ecef5e97d9b006ac385f77933930c42769cc35ca8ddffe` |
|
||||
| `crates/oa-desktop/src/style/tests/readonly.rs` | `6097ab8984ce61c90fc725c01c758391a30009004f0b1d42a4ebf4fd8b4106d9` |
|
||||
| `crates/oa-desktop/src/hosted/view.rs` | `862180b048e6ae28fa188aa889ab5b584061604c7cd7670ab83ec4dbf8c9ae91` |
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
{
|
||||
"kind": "proposed opaque source colors; no native or compiled evidence",
|
||||
"candidates": {
|
||||
"S1_keep_BG": {
|
||||
"surface_rgb": [
|
||||
0.945,
|
||||
0.956,
|
||||
0.969
|
||||
],
|
||||
"selection_hex": "7A8C85",
|
||||
"selection_surface_ratio": 3.21039827873176,
|
||||
"tokens": [
|
||||
{
|
||||
"class": "keyword",
|
||||
"hex": "29123A",
|
||||
"normal_ratio": 15.296950856346303,
|
||||
"selected_ratio": 4.7648140598895505
|
||||
},
|
||||
{
|
||||
"class": "identifier",
|
||||
"hex": "031F32",
|
||||
"normal_ratio": 15.247586151461915,
|
||||
"selected_ratio": 4.749437554983159
|
||||
},
|
||||
{
|
||||
"class": "string",
|
||||
"hex": "032215",
|
||||
"normal_ratio": 15.277172268781037,
|
||||
"selected_ratio": 4.758653270526967
|
||||
},
|
||||
{
|
||||
"class": "number",
|
||||
"hex": "331404",
|
||||
"normal_ratio": 15.28866031189317,
|
||||
"selected_ratio": 4.762231656170966
|
||||
},
|
||||
{
|
||||
"class": "comment",
|
||||
"hex": "1A1D20",
|
||||
"normal_ratio": 15.313946656029987,
|
||||
"selected_ratio": 4.770108044687723
|
||||
},
|
||||
{
|
||||
"class": "error",
|
||||
"hex": "3E060F",
|
||||
"normal_ratio": 15.347980370981265,
|
||||
"selected_ratio": 4.780709132775997
|
||||
},
|
||||
{
|
||||
"class": "punctuation_and_whitespace",
|
||||
"hex": "191D23",
|
||||
"normal_ratio": 15.303767187632298,
|
||||
"selected_ratio": 4.766937264144659
|
||||
},
|
||||
{
|
||||
"class": "fallback_and_caret",
|
||||
"hex": "1A1E22",
|
||||
"normal_ratio": 15.164745919674079,
|
||||
"selected_ratio": 4.723633830773414
|
||||
}
|
||||
]
|
||||
},
|
||||
"S2_white_editor": {
|
||||
"surface_rgb": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"selection_hex": "80958B",
|
||||
"selection_surface_ratio": 3.1868237541712676,
|
||||
"tokens": [
|
||||
{
|
||||
"class": "keyword",
|
||||
"hex": "331748",
|
||||
"normal_ratio": 15.476500976331574,
|
||||
"selected_ratio": 4.8564031682248565
|
||||
},
|
||||
{
|
||||
"class": "identifier",
|
||||
"hex": "04273D",
|
||||
"normal_ratio": 15.409967009213712,
|
||||
"selected_ratio": 4.835525337428354
|
||||
},
|
||||
{
|
||||
"class": "string",
|
||||
"hex": "042A1B",
|
||||
"normal_ratio": 15.530539738748914,
|
||||
"selected_ratio": 4.873360102961711
|
||||
},
|
||||
{
|
||||
"class": "number",
|
||||
"hex": "3F1A06",
|
||||
"normal_ratio": 15.421447620267793,
|
||||
"selected_ratio": 4.839127862054654
|
||||
},
|
||||
{
|
||||
"class": "comment",
|
||||
"hex": "212529",
|
||||
"normal_ratio": 15.426285095510266,
|
||||
"selected_ratio": 4.840645823390339
|
||||
},
|
||||
{
|
||||
"class": "error",
|
||||
"hex": "4C0914",
|
||||
"normal_ratio": 15.481289731174558,
|
||||
"selected_ratio": 4.857905841485878
|
||||
},
|
||||
{
|
||||
"class": "punctuation_and_whitespace",
|
||||
"hex": "20252C",
|
||||
"normal_ratio": 15.413837344097145,
|
||||
"selected_ratio": 4.836739817795637
|
||||
},
|
||||
{
|
||||
"class": "fallback_and_caret",
|
||||
"hex": "212529",
|
||||
"normal_ratio": 15.426285095510266,
|
||||
"selected_ratio": 4.840645823390339
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"input_hashes": [
|
||||
{
|
||||
"path": ".local\\palette-selection-remedy-20260912.py",
|
||||
"sha256": "7ec45627fe02368c2239d3ecc75ac5f32a8bec155522718b291f78bd19c0c160"
|
||||
},
|
||||
{
|
||||
"path": "crates\\oa-desktop\\src\\highlight.rs",
|
||||
"sha256": "da7c254fff7052e5fd9ec7469f473b91a819406028d7c304f6e22c4169561600"
|
||||
},
|
||||
{
|
||||
"path": "crates\\oa-desktop\\src\\style.rs",
|
||||
"sha256": "05d61bec16141612ccafa0988e8c4827f41754509efdb3b961eab69127ded896"
|
||||
},
|
||||
{
|
||||
"path": "crates\\oa-desktop\\src\\views.rs",
|
||||
"sha256": "9b53126d19b2ae34f170a1422c9f5ea5a833489274cbfa04546279058e289f9b"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\iced_widget-0.14.2\\src\\text_editor.rs",
|
||||
"sha256": "cecf8686c90aa92066d55075beba545dfb7dd5a44bad0c1519fcb110a56070e1"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\iced_core-0.14.0\\src\\text\\highlighter.rs",
|
||||
"sha256": "b92800869452e91668e588654f2356b58994cf2e00ffd9b6e0b48ef21e627387"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\iced_graphics-0.14.0\\src\\text\\editor.rs",
|
||||
"sha256": "b12c4e645a4db26c79e421a88c2127c8c20b7ff0f5a6fae9a8e050f40bf39567"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\cosmic-text-0.15.0\\src\\edit\\mod.rs",
|
||||
"sha256": "e1c91dfeda328e70919005eb35883645307aeb913913913f752b6e19e0c0c70a"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\iced_core-0.14.0\\src\\theme.rs",
|
||||
"sha256": "99944939e7b88c319e5ed668458a835b05d05f675931ff796cc85d59cecc4b13"
|
||||
},
|
||||
{
|
||||
"path": ".local\\toolchain\\cargo\\registry\\src\\index.crates.io-1949cf8c6b5b557f\\iced_core-0.14.0\\src\\theme\\palette.rs",
|
||||
"sha256": "8d674d623f570e93c7ba862f0ece6f1ae363b8b5514b69b0f0743d02ee2d56a4"
|
||||
}
|
||||
],
|
||||
"range_aware_hypothesis": {
|
||||
"selection_hex": "00665C",
|
||||
"selected_foreground_hex": "FFFFFF",
|
||||
"fill_BG_ratio": 6.218258219372413,
|
||||
"selected_text_ratio": 6.873774963544523,
|
||||
"stale_selected_text_on_blurred_white_surface": 1.0
|
||||
},
|
||||
"wrong_fallback_controls": {
|
||||
"S1_keep_BG": 3.990758274297769,
|
||||
"S2_white_editor": 4.444090028375658
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Source-editor selection remedy and centralized control styling
|
||||
|
||||
2026-09-12. Bounded source investigation for root-owned issue #26, following the [independent palette critique](palette-readability-independent-critique-20260912.md). No production edit, compiler, native UI interaction, or Gitea mutation. The [dependency-free calculator](palette-selection-remedy-20260912.py) was executed successfully; its [JSON receipt](palette-selection-remedy-20260912.json) records candidate ratios and hashes of the actual source/API inputs. Neither candidate has been rendered or accepted.
|
||||
|
||||
## Recommended implementation boundary
|
||||
|
||||
The simplest feasible color remedy is **one explicit source-editor style plus a static dark syntax palette**. It avoids selection-coordinate tracking, custom rendering and a dependency fork. Prefer the concrete S2 candidate below for the first native trial: a white editor surface, medium selection fill `#80958B`, and the seven dark syntax colors listed below. White is already an established application surface. It permits somewhat brighter token colors than preserving current BG at the same selection/foreground benchmarks.
|
||||
|
||||
Use `Theme::custom_with_fn` centrally for ordinary generated control colors, where the roles are shared safely. Keep the source editor's special surface/selection/value in one `.style(style::source_editor)` adapter. This hybrid is smaller than putting bespoke adapters on every Inspector field and avoids forcing source-editor contrast constraints onto all shared theme pairs. It is a refinement of the earlier audit's suggested implementation boundary.
|
||||
|
||||
This resolves the **color/API feasibility gap for selections that Iced actually paints**. Existing newline-only selection geometry and loss of selection paint on blur remain separate limitations below; do not claim a palette guarantees every selected range is always visible.
|
||||
|
||||
## Concrete static candidates
|
||||
|
||||
The engineering targets here are 3:1 for the selection fill against its actual adjacent editor surface and 4.5:1 for every glyph color against both surfaces. These are explicit candidate criteria, not a claim that WCAG universally requires 3:1 between every state. Syntax class identity, lexer spans, text, monospace font and size remain unchanged. Colors darken existing purple/blue/green/brown/red families; they do not neutralize diagram category colors.
|
||||
|
||||
| Candidate | Normal editor surface | Selection fill | Fill/surface | Minimum selected glyph contrast | Minimum normal glyph contrast |
|
||||
|---|---|---|---:|---:|---:|
|
||||
| S1: preserve current background | source floats `(.945,.956,.969)` | `#7A8C85` | 3.210398 | 4.723634 | 15.164746 |
|
||||
| **S2: white source surface** | `#FFFFFF` | `#80958B` | 3.186824 | 4.835525 | 15.409967 |
|
||||
|
||||
S2's exact proposed formatter/value colors:
|
||||
|
||||
| Role | Color | Normal surface contrast | Selection contrast |
|
||||
|---|---|---:|---:|
|
||||
| Keyword | `#331748` | 15.476501 | 4.856403 |
|
||||
| Identifier | `#04273D` | 15.409967 | 4.835525 |
|
||||
| String | `#042A1B` | 15.530540 | 4.873360 |
|
||||
| Number | `#3F1A06` | 15.421448 | 4.839128 |
|
||||
| Comment | `#212529` | 15.426285 | 4.840646 |
|
||||
| Error token | `#4C0914` | 15.481290 | 4.857906 |
|
||||
| Punctuation/whitespace | `#20252C` | 15.413837 | 4.836740 |
|
||||
| Widget value fallback and caret | `#212529` | 15.426285 | 4.840646 |
|
||||
|
||||
The fallback is essential: A's ordinary ink `#202C3A` is only **4.444090:1** on S2 selection (3.990758 on S1). Assigning selection fill while leaving all other editor defaults unchanged does not implement this candidate. Use the same dark fallback for any visible unformatted text, and explicitly choose a readable placeholder. Leave font mapping and lexer behavior intact.
|
||||
|
||||
The candidate generation scaled each current token's linear RGB channels to target relative luminance 0.018 (S2) or 0.012 (S1), then rounded to the listed byte colors. Ratios are recomputed from those final bytes. This keeps approximate linear chromaticity before rounding; it does **not** prove perceived hue or scanability is preserved on a limited-color display. Production needs only the final constants, not runtime color derivation.
|
||||
|
||||
The tradeoff is inherent. For dark text, an intermediate darker selection and a lighter normal surface, their contrast ratios multiply. Requiring selection/surface >=3 and text/selection >=4.5 implies text/normal >=13.5. Consequently a static palette satisfying both benchmarks has very dark syntax colors. A native trial must establish whether the remaining token color distinctions help enough. The higher normal contrast is readable arithmetic, not evidence that the syntax appearance is optimal.
|
||||
|
||||
## Actual public API path
|
||||
|
||||
`views.rs:1187` already constructs a single `text_editor(&app.source_editor)` and applies `highlight_with::<ArcadiaHighlighter>(app.source_snapshot.clone(), highlight::format)`. Installed `iced_widget-0.14.2/src/text_editor.rs:323` exposes `.style(impl Fn(&Theme, Status) -> Style)`. `Style` exposes background, border, placeholder, value and selection. A small function in `style.rs` can start from `text_editor::default(theme, status)`, set S2 background/value/placeholder/selection, and preserve or explicitly set the existing status-dependent border. The exact implementation should ensure a focused border is still visible against the chosen white surface.
|
||||
|
||||
`highlight.rs:62` directly maps all token classes to `Format { color: Some(...), font: None }`. Replacing those seven colors implements the static syntax half. No change to `ArcadiaHighlighter::Settings`, tokenization, source snapshots, `SourceEdit`, selection actions or file persistence is needed. The selected and normal foregrounds are the same, so blur, selection reversal and rerender cannot leave a stale light foreground on a light background. Source code and graph category coloring remain independent.
|
||||
|
||||
This is a source-feasible sketch, not compiled proof. The normal integration gate must still exercise actual types and resolved styles. Use a dedicated source selection token rather than overwriting navigation's much lighter selection fill; those roles have different adjacent surfaces and content.
|
||||
|
||||
## Why range-aware selected foreground is not the first implementation
|
||||
|
||||
There is a partial public path: `Highlighter::Settings` is any `PartialEq + Clone` type, and `Highlight` is unconstrained (`iced_core-0.14.0/src/text/highlighter.rs`). Settings could carry a source snapshot and selection; token spans could be split into selected/unselected fragments. A richer Highlight would let the formatter return white selected glyphs over an accent selection fill. `text_editor.rs:637` calls `update` when settings change, and the graphics editor formats the resulting spans. Numerically white on A accent `#00665C` is **6.873775:1**, while the fill against current BG is **6.218258:1**. It would preserve ordinary syntax colors outside selection and temporarily suppress hue within selected text.
|
||||
|
||||
However, the current API does not provide a simple, complete logical selected-range contract to feed that mechanism:
|
||||
|
||||
- `Content::cursor()` (`text_editor.rs:426`) returns `Cursor { position, selection }`. In `iced_graphics-0.14.0/src/text/editor.rs:277`, its selection comes from the raw cosmic-text Normal/Word/Line anchor, with the kind discarded. Columns are UTF-8 byte indexes, not character counts (`cosmic-text-0.15.0/src/cursor.rs`).
|
||||
- Actual selection painting uses cosmic-text `selection_bounds()`, which expands Word and Line selections (`cosmic-text/src/edit/mod.rs:217`). Double-click sets `Selection::Word(self.cursor)`; triple-click sets Line. Thus raw position and anchor can be identical while a complete word or line is painted selected. Sorting the exposed endpoints would incorrectly produce an empty highlight range.
|
||||
- `Content::selection()` returns copied text, not canonical range endpoints or selection kind. Searching that string in source is ambiguous for repeated fragments. Duplicating Unicode word/line expansion and remembering every action would introduce a second selection implementation and lifecycle risks.
|
||||
- The range fill is drawn only while the widget is focused (`text_editor.rs:1021`). White selected-token formatting that remains after blur would become unreadable. Highlighter formatting receives class/theme, not widget status. Widget State has focus information, but Content does not expose it as a simple synchronized view setting. A custom operation/wrapper could acquire more state, at additional timing and invalidation cost.
|
||||
- Selection-only setting changes would require rehighlighting affected spans. The present highlighter `update` rebuilds tokenized lines; simply extending its settings would retokenize the source during selection drags. A correct optimized version would separate token cache invalidation from selection invalidation and verify changes both entering and leaving a range, including offscreen lines. That is unnecessary work for the static candidate.
|
||||
|
||||
These are concrete correctness/performance obligations, not a claim that range-aware formatting is impossible without a fork. Prefer it only if the static candidate fails the color-class/native reading task and the additional selection/focus contract can be supplied without copying editor behavior.
|
||||
|
||||
A third small option is to switch **all** syntax colors to the dark palette whenever a selection exists, returning to stronger original hues afterward. It avoids exact selected endpoints but changes unselected text too, creates visible palette changes during selection, and adds settings invalidation without improving selected-text color distinction. It is a possible later experiment, not the preferred first implementation.
|
||||
|
||||
## Palette cannot repair absent selection geometry
|
||||
|
||||
`iced_graphics/src/text/editor.rs:781` computes range widths from glyph ranges. Empty ranges yield width zero; `selection()` drops zero-width rectangles. A selection containing only a line ending can therefore have no painted range quads, while the widget is in the Range branch and draws no caret. Empty intermediate lines also lack fill. This is a source-derived prediction, not a witnessed native failure in this task.
|
||||
|
||||
Keep newline-only, empty-line and trailing-whitespace cases in the native evaluator. If the requirement is that every selection—including only a newline—must be visible, neither S1 nor S2 fulfills that universally through colors alone. Preserve a separate geometry finding and assess an upstream/small rendering extension against an actual witness; do not add a fake outline to an API that cannot draw it. Losing the selection fill when focus leaves the editor is also existing behavior; verify that refocusing restores the range and that copy/edit operations remain understandable.
|
||||
|
||||
## Centralized Extended palette: feasible with a small hybrid boundary
|
||||
|
||||
Installed `iced_core-0.14.0/src/theme.rs:100` exposes:
|
||||
|
||||
```rust
|
||||
Theme::custom_with_fn(name, palette, |palette| {
|
||||
let mut extended = iced::theme::palette::Extended::generate(palette);
|
||||
// Set reviewed shared pairs here; return extended.
|
||||
extended
|
||||
})
|
||||
```
|
||||
|
||||
`Extended`, its nested role fields and `Pair { color, text }` are public. `Pair::new(color, text)` uses Iced's readable-text adjustment; a direct Pair literal preserves exact values but requires explicit contrast verification. No custom Theme type or theme framework is needed. The generator runs at theme creation and existing default controls read the resulting extended palette.
|
||||
|
||||
| Extended role | Actual consumers / coupled obligations |
|
||||
|---|---|
|
||||
| `background.base` | Text-input/editor background and fallback text; unchecked active checkbox background. Keep the pair coherent. Source S2 overrides its own surface/value. |
|
||||
| `background.weak` | Pick-list and popup-menu fill/text, disabled-input fill, hovered unchecked checkbox fill, scrollbar rail. It is a surface, not merely a faint border token. |
|
||||
| `background.strong.color` | Ordinary input/editor/pick-list/menu border, checkbox outline; also disabled checked-checkbox fill and some button outlines. Darkening it is feasible but has those extra effects. |
|
||||
| `secondary.base.color` | Input/editor/pick-list placeholder; also secondary-button background. Set its `.text` coherently and inspect actual buttons. |
|
||||
| `primary.weak.color` | Default input/editor selection fill. Those widgets do not consume the pair's `.text` as selected foreground. Source S2 should override this role locally. |
|
||||
| `primary.strong` | Focus/opened borders, primary-button hover fill and menu selected background/text. Menu uses the strong pair's text; hovered primary buttons retain **base** text on strong fill. |
|
||||
|
||||
Evidence: `text_input.rs:1758`, `text_editor.rs:1466`, `pick_list.rs:904`, `overlay/menu.rs:645`, `button.rs:599,612`, `checkbox.rs:569` in installed iced_widget 0.14.2. Checked hover checkboxes also mix primary base text with strong fill. Pair readability in isolation does not prove these cross-state combinations.
|
||||
|
||||
**Recommendation:** centralize shared opaque surface, secondary text, boundary and interaction pairs in `style::theme()`; retain one source-editor override; add another local adapter only when a genuinely incompatible consumer is found. Do not globally darken `background.weak` as a border fix, and do not assume setting `primary.weak.text` repairs input selection. Ordinary input value/selection pairs still need their own resolved-color check; S2's token colors must not be copied into every control by accident.
|
||||
|
||||
## Native and integration acceptance cases
|
||||
|
||||
1. Resolve actual production source-editor styles for active/hover/focused/disabled states and all seven actual formatter outputs. Check S2's stated surfaces and ratios without rounding. Include the wrong A-ink fallback as a negative control. Verify any centralized theme's default input, pick-list, menu, checkbox and button cross-state pairs, not just Pair objects.
|
||||
2. Open a representative source file at the actual 14 px monospace setting, native minimum window and ordinary working size/DPI. Read and edit a declaration, string, number and multi-line comment before and during selection. Judge whether near-black category colors still aid scanning; preserve diagnostic text/error identification independently of red hue.
|
||||
3. Select part of a token, a whole word by double-click, a line by triple-click, and multiple lines in both directions. Include non-ASCII identifiers, combining characters, tabs, wrapped lines, empty lines, newline-only selection and trailing whitespace. Verify the visible selection agrees with copied text and actual replacement, recording geometry limitations rather than hiding them.
|
||||
4. Extend selection with the keyboard, scroll it out and back, transfer focus to another control and return, then copy/paste, undo/redo and switch source files. Verify no stale token attributes, missing text or unintended source edits. A static palette should introduce no selection-specific retokenization or extra source invalidations.
|
||||
5. Use original native screenshots plus separately labeled luminance-grayscale/degradation analysis; check range boundaries, glyph readability and focus separately. Record source/build, DPI, window size and analysis parameters. A numerical pass is not acceptance on the user's monitor.
|
||||
|
||||
S2 is a concrete, small candidate for the next authorized production/native cycle. Its numerical feasibility is established; its visual quality, source-control integration and native behavior remain unverified.
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Dependency-free candidate arithmetic only; writes JSON to stdout, no rendering."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def rgb(value):
|
||||
return tuple(int(value[i:i + 2], 16) / 255 for i in (0, 2, 4))
|
||||
|
||||
|
||||
def luminance(color):
|
||||
return sum(w * (v / 12.92 if v <= .04045 else ((v + .055) / 1.055) ** 2.4)
|
||||
for w, v in zip((.2126, .7152, .0722), color))
|
||||
|
||||
|
||||
def ratio(first, second):
|
||||
low, high = sorted((luminance(first), luminance(second)))
|
||||
return (high + .05) / (low + .05)
|
||||
|
||||
|
||||
BG = (.945, .956, .969)
|
||||
WHITE = rgb('FFFFFF')
|
||||
CANDIDATES = {
|
||||
'S1_keep_BG': (BG, '7A8C85', {
|
||||
'keyword': '29123A', 'identifier': '031F32', 'string': '032215',
|
||||
'number': '331404', 'comment': '1A1D20', 'error': '3E060F',
|
||||
'punctuation_and_whitespace': '191D23', 'fallback_and_caret': '1A1E22',
|
||||
}),
|
||||
'S2_white_editor': (WHITE, '80958B', {
|
||||
'keyword': '331748', 'identifier': '04273D', 'string': '042A1B',
|
||||
'number': '3F1A06', 'comment': '212529', 'error': '4C0914',
|
||||
'punctuation_and_whitespace': '20252C', 'fallback_and_caret': '212529',
|
||||
}),
|
||||
}
|
||||
|
||||
result = {'kind': 'proposed opaque source colors; no native or compiled evidence',
|
||||
'candidates': {}, 'input_hashes': []}
|
||||
for name, (surface, selection_hex, tokens) in CANDIDATES.items():
|
||||
selection = rgb(selection_hex)
|
||||
pairs = [{'class': cls, 'hex': value,
|
||||
'normal_ratio': ratio(rgb(value), surface),
|
||||
'selected_ratio': ratio(rgb(value), selection)}
|
||||
for cls, value in tokens.items()]
|
||||
fill_ratio = ratio(selection, surface)
|
||||
assert fill_ratio >= 3
|
||||
assert all(p['normal_ratio'] >= 4.5 and p['selected_ratio'] >= 4.5 for p in pairs)
|
||||
result['candidates'][name] = {
|
||||
'surface_rgb': surface, 'selection_hex': selection_hex,
|
||||
'selection_surface_ratio': fill_ratio, 'tokens': pairs,
|
||||
}
|
||||
result['range_aware_hypothesis'] = {
|
||||
'selection_hex': '00665C', 'selected_foreground_hex': 'FFFFFF',
|
||||
'fill_BG_ratio': ratio(rgb('00665C'), BG),
|
||||
'selected_text_ratio': ratio(WHITE, rgb('00665C')),
|
||||
'stale_selected_text_on_blurred_white_surface': ratio(WHITE, WHITE),
|
||||
}
|
||||
result['wrong_fallback_controls'] = {
|
||||
name: ratio(rgb('202C3A'), rgb(selection))
|
||||
for name, (_, selection, _) in CANDIDATES.items()
|
||||
}
|
||||
paths = [Path(__file__), Path('crates/oa-desktop/src/highlight.rs'),
|
||||
Path('crates/oa-desktop/src/style.rs'), Path('crates/oa-desktop/src/views.rs')]
|
||||
registry = Path('.local/toolchain/cargo/registry/src/index.crates.io-1949cf8c6b5b557f')
|
||||
paths += [registry / path for path in (
|
||||
'iced_widget-0.14.2/src/text_editor.rs', 'iced_core-0.14.0/src/text/highlighter.rs',
|
||||
'iced_graphics-0.14.0/src/text/editor.rs', 'cosmic-text-0.15.0/src/edit/mod.rs',
|
||||
'iced_core-0.14.0/src/theme.rs', 'iced_core-0.14.0/src/theme/palette.rs')]
|
||||
for path in paths:
|
||||
result['input_hashes'].append({'path': str(path),
|
||||
'sha256': hashlib.sha256(path.read_bytes()).hexdigest()})
|
||||
print(json.dumps(result, indent=2))
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Routing warning selector: source inspection and bounded alternatives
|
||||
|
||||
Reviewed source: `092f5244b9264b53bccea884a1a4fe9d860f2ad2`, branch `codex/palette-canvas-956atf-20260913`, worktree `palette-canvas-20260913/`. The completed palette-consumer commit is unchanged and remains independently cherry-pickable. This follow-up makes **no production edits and no new commit**. No compiler, tests, UI, or Gitea operations were run. Parent explicitly narrowed the follow-up to this design/reuse inspection after the API gaps below were identified.
|
||||
|
||||
## What the existing APIs actually provide
|
||||
|
||||
All source locations below are relative to that exact worktree/revision.
|
||||
|
||||
- `crates/oa-desktop/src/diagram.rs:59–85,460–526`: the authoritative displayed `RoutedView` belongs to private `DiagramCanvas::State.cache`; draw and hit testing share `scene(state)`. Allocation preview uses a separate preview cache. Node movement, route, and port previews participate in this scene construction. The host cannot read the exact scene or its private held node/route/port/pan/connection gestures. An App-side `route_diagram` call on every view rebuild would duplicate the router and could disagree with the preview currently displayed.
|
||||
- `crates/oa-desktop/src/views.rs:569–603`: an existing 30-unit host strip preserves canvas size and gives connection/allocation, promoted-endpoint expansion, or routing guidance. It computes a projected view, not a routed view. It therefore cannot currently populate an accurate warning count/control without another interface.
|
||||
- `crates/oa-desktop/src/diagram.rs:1424–1450`: the current canvas warning strip already draws warning text or an actionable-route count from the actual scene. Its selected-route branch suppresses the selected suspended custom route's warning; its fallback may still report other warnings. `PromotedEndpoints` is excluded from the count. At `:1165`, selected/highlighted edges take the TEAL branch before the amber-warning branch. The selector must therefore include selected warning routes independently of their painted color.
|
||||
- `crates/oa-desktop/src/model_menu.rs:11–90`: `Request` has no warning catalog or menu purpose; `Opened` does protect document identity, document revision, input epoch, page, layer, current diagram, and originating canvas context. These protections should be reused.
|
||||
- `crates/oa-desktop/src/app.rs:1943–1965`: ordinary `MenuOpen` intentionally clears the connection session and advances `canvas_epoch`, invalidating private canvas pointer history. It cannot simply become a host warning-open action while promising preservation of held gestures. At `:1972–2008`, invocation validates the token/context, then selects `request.object` before dispatching its action. Using the diagram as a catalog anchor would select it before the intended relation. At `:1010`, ordinary selection calls `apply()`. A warning chooser must reject pending drafts before reaching this path; merely disabling a painted menu row does not establish an update-handler guard.
|
||||
- `crates/oa-desktop/src/context_menu.rs:23–87,212–229`: the existing popup has scrollable actions, keyboard arrows/Home/End, Enter/Space, and Escape. It has **no search query or search input**. Adding searchable entries is a real widget contract change, not an existing capability.
|
||||
- `crates/oa-desktop/src/forms/reference_chooser.rs:17,151` and `forms/reference_chooser/adapter.rs:60`: the live searchable reference chooser has draft/record/property-path identity and returns staged-value Write intents. `forms/reference_chooser/widget.rs:157–196` is generic over the outer message but accepts that concrete form chooser state and hardcodes the reference-search placeholder. It is not a ready navigation chooser. Its historical “experiment / cfg(test)” module comment is stale; `forms.rs:21` and App messages actually wire it into production. Reuse by fabricating a draft/path context would hide the semantic mismatch.
|
||||
|
||||
## Smallest materially different alternatives
|
||||
|
||||
| Alternative | Concrete implementation boundary | Exact acceptance limitations |
|
||||
| --- | --- | --- |
|
||||
| A. Add a warning section to the existing **canvas-originated context menu** | At the existing canvas right-click or ContextMenu/Shift-F10 open site, take relation IDs and actionable warning text from `self.scene(state)`. Carry a typed catalog/purpose through the existing guarded menu; choose a listed relation directly without first selecting the diagram. Reject warning-menu entry during a held gesture. Use existing scrolling/navigation and explicit relation name, alias, endpoint names, and stable identity details for ambiguous names. | Avoids a cross-widget scene bridge and introduces no second router or search framework. It does **not** deliver the requested persistent host warning-count control or text search. Existing top-level Actions menus cannot safely synthesize this catalog. Long lists remain slower to browse. Pointer context-menu discoverability is weaker than a dedicated control. This is a consciously smaller interim contribution, not full acceptance. |
|
||||
| B. Deliver the selected fixed-host searchable selector | Add one explicit canvas-to-host presentation/interaction interface and a typed warning-menu purpose; add opt-in query/filter behavior to the existing menu. Preserve the 30-unit host row and keep expansion/tool guidance beside or ahead of the control. Reuse existing menu context guards and direct guarded relation selection. | Meets the requested outcome only after fresh scene/gesture synchronization, input ownership, stale-query, and dismissal tests. Touches at least `diagram.rs`, `views.rs`, `app.rs`, `model_menu.rs`, and `context_menu.rs` plus tests, rather than the original four palette files. A cached warning list alone cannot establish held-gesture safety. This is a separate interaction change needing an explicit interface decision and independent review. |
|
||||
| C. Reuse the reference chooser by extracting its navigation-neutral state/overlay | Separate catalog/search/navigation/dismissal from draft-reference compatibility and staged writes, then supply distinct host adapters. It would retain the mature searchable overlay rather than adding search to the menu. | Still needs B's authoritative scene/gesture bridge and host guards. Changes form chooser identity/focus behavior and broadens regression scope to Inspector/Review reference editing. No known second non-form consumer justifies this extraction for a bounded palette task. Reject for now; do not fake form contexts to avoid the extraction. |
|
||||
|
||||
**Recommendation:** keep 092 moving through palette integration. If the full selector remains the next priority, choose B as an explicitly scoped interaction task; A is useful only if the parent deliberately accepts its missing persistent control and search. Do not silently substitute A for the requested selector. C adds unrelated form risk. An on-route glyph remains rejected because existing label/arrow geometry provides no safe placement contract (`crates/oa-core/src/routing.rs:47,1269`; desktop `diagram.rs:372,1208`).
|
||||
|
||||
## Minimum interface decisions before B
|
||||
|
||||
The canvas must publish or otherwise expose a read-only **presentation snapshot**, derived from its existing scene, containing originating `route_edit::Context`, an identity for the scene/presentation, actionable route IDs and their warning reasons, and whether a gesture prevents opening. No second routed-scene cache or model mutation is needed merely to represent this list. Count **routes**, not individual warning reasons; omit routes whose only warning is `PromotedEndpoints`. Include a route with both promotion and another actionable reason, preserving expansion guidance and excluding its promotion reason from the actionable explanation.
|
||||
|
||||
A delayed previously-idle snapshot is insufficient: a node/port/route press does not advance document revision immediately. An open request must be revalidated against current canvas interaction ownership, or the same ordered event path must invalidate the opening capability when any gesture begins. Specify that ordering before implementing a Redraw-only publication. Similarly, opening must not reuse the ordinary connection-clearing/epoch-advancing path until active gestures and connection/allocation tools have been rejected without changes. The control can remain visible but disabled during those states and pending edits.
|
||||
|
||||
Menu invocation must validate its exact token, document/view/revision/epoch, catalog identity and chosen route membership; recheck busy/pending model/source/behavior state in the handler. Only then dispatch relation selection. Query changes need a result-generation rule so an old row event cannot choose a different newly filtered entry; Escape and ordered keyboard navigation must remain usable after typing. Preserve existing dismissal-release swallowing and focus restoration. These are interface invariants, not a proposal for a new general menu framework.
|
||||
|
||||
## Evaluators needed for a future implementation
|
||||
|
||||
1. Produce real routed scenes with duplicate relation names, an empty relation label, a selected actionable warning route, a normal route, a promotion-only route, and mixed promotion/actionable warnings. Verify exact catalog membership, reasons, route count, disambiguation, and search fields. A test that manually creates only the proposed warning DTO would not establish same-scene correctness.
|
||||
2. Drive the actual canvas update path through node, port, bend/label, pan, connection, and allocation presses/previews, then request the host selector before release. Assert it cannot cancel or commit the held operation. Include a queued stale idle snapshot and pending invalid Inspector/source/behavior drafts. Compare draft bytes, document revision/history, route and port data, and session/gesture continuity before and after.
|
||||
3. Drive actual App/menu open, query, invoke, and close handling. Falsify old tokens and stale same-ID document replacement, diagram/page/layer change, geometry/fold/filter revision, and canvas epoch. Test an entry that ceased being warned/visible, query-then-old-row activation, and no accidental intermediate diagram selection. No forged inner `Message::Select` should bypass membership validation.
|
||||
4. Exercise actual popup keyboard search/navigation/Enter and Escape/outside dismissal, including queued input and mouse release. Do not use only catalog helper tests as evidence for focus or release ownership.
|
||||
5. Native acceptance after integration: fixed measured canvas size with zero/one/many warnings, minimum window, selected warning edge, duplicate/unlabeled routes, suspended custom route expansion guidance, long reasons, and keyboard/text focus recovery. Verify source/history/routes are unchanged by browsing and selection. Until these are run, neither discoverability nor interaction acceptance is established.
|
||||
|
||||
The old “select an amber line” instruction and warning rendering remain unchanged because no new control has been wired. This report supersedes only the earlier handoff's estimate that the selector was a small host/menu wiring change; it does not retract the candidate's user-facing benefit or claim the requirement complete.
|
||||
@@ -0,0 +1,37 @@
|
||||
# Palette readability candidate
|
||||
|
||||
Source **afa74a305980f611437dd3aa2f937d36e9266cfe** combines the reviewed palette,
|
||||
canvas boundaries, process topology outlines and read-only input correction with
|
||||
the lifecycle/typed-activation integration. The Windows `style::tests` gate passes
|
||||
all **10 tests**, with no failures or ignored cases and 862 filtered out. It
|
||||
compiled in 7m20s and ran in 3.85s, closing 2026-09-13 at 00:37:15 UTC with exit 0.
|
||||
All **538 committed inputs** remained unchanged; input-manifest SHA-256 is
|
||||
`d7dc0bd3b642e50c2796035696fc6da5bb0cbf07e680e15528c49229544c5583`.
|
||||
[Exact command and result](experiments/diagram-removal-windows-20260912/palette-afa74a3.result.json)
|
||||
and [raw output](experiments/diagram-removal-windows-20260912/palette-afa74a3.log)
|
||||
retain the test identities. This targeted result does not establish full-suite,
|
||||
native interaction or physical-monitor acceptance.
|
||||
|
||||
The [decision and acceptance boundary](experiments/palette-readability-20260913/palette-integration-decision-20260913.md)
|
||||
records why the first candidate strengthens essential luminance contrast while
|
||||
retaining category cues and lighter decorative dividers. The Source editor uses
|
||||
a separate static dark syntax/selection palette. The read-only input adapter
|
||||
retains selection and copy while the missing input callback still prevents edits.
|
||||
|
||||
- [Source audit and candidate comparison](experiments/palette-readability-20260913/palette-readability-audit-20260912.md)
|
||||
- [Independent critique and arithmetic](experiments/palette-readability-20260913/palette-readability-independent-critique-20260912.md)
|
||||
- [Source-selection alternatives and API evidence](experiments/palette-readability-20260913/palette-selection-remedy-20260912.md)
|
||||
- [Foundation correction and actual-widget evaluator](experiments/palette-readability-20260913/palette-readonly-correction-source-handoff-20260913.md)
|
||||
- [Independent correction review and residual consumers](experiments/palette-readability-20260913/palette-correction-independent-review-20260913.md)
|
||||
- [Routing-warning selector scope and alternatives](experiments/palette-readability-20260913/routing-warning-selector-scope-20260913.md)
|
||||
- [Exact archived payload identities](experiments/palette-readability-20260913/manifest.json)
|
||||
|
||||
The archive preserves historical source-only findings verbatim. In particular,
|
||||
the original foundation's false claim that disabled inputs cannot select is
|
||||
superseded by its correction and independent review. Numeric contrast tests are
|
||||
engineering benchmarks, not proof that all physical monitors display the result
|
||||
well. Native syntax scanability, thin strokes at Fit, keyboard focus and actual
|
||||
selection remain separate acceptance observations. Custom chooser/menu keyboard
|
||||
target cues and faint control boundaries identified in the residual review need
|
||||
their bounded correction and combined verification. Routing-warning selection
|
||||
remains a separate unfinished interaction change. Issue #26 remains open.
|
||||
Reference in New Issue
Block a user