Skip to content

Commit 53c1039

Browse files
committed
fix(web): #106 review — comments and every other syntax colour can be read in both themes
Both themes' syntax palettes (One Dark / One Light) had colours below WCAG AA's 4.5:1 on their own editor background. Each one is moved in HSL lightness only (hue and saturation kept, within 2 degrees after rounding) to the first value that reaches 4.5:1. theme.test.js now holds every tokenColors and semanticTokenColors foreground of both themes to 4.5:1 on the theme's editor.background (an alpha colour composited first); it fails on the previous themes, listing each colour. Dark (editor #1b1c22), review comment 4274117784: - comments, markup quotes, doc comments / doctype #5c6370 2.81 -> #7b8493 4.50 Light (editor #ffffff), review comment 4274117904: - comments, markup quotes #a0a1a7 2.58 -> #75767e 4.52 - strings, inherited class, inline/fenced code #50a14f 3.21 -> #428541 4.50 - regexp, escapes, support types #0184bc 4.18 -> #017eb4 4.52 - variables, properties, tags, headings, JSON keys #e45649 3.67 -> #df3425 4.51 - functions, decorators, links #4078f2 4.05 -> #326ef1 4.53 - classes, types, CSS tags #c18401 3.20 -> #9f6d01 4.50 - constants, attribute names, bold #986801 4.87 -> #765101 7.12 (passed already; darkened with the class colour, which shares its hue and would otherwise be indistinguishable from it: CIELAB delta-E 2.9, against 16.2 before and 16.9 now) Token backgrounds: the editor does not paint them ("Token background colors are currently not supported", colorThemeSchema.ts), so the light theme's `invalid` scope (white on a #e45649 background) was white text on the white editor, 1.00:1. `invalid` is now the theme's red text in both themes (light #df3425 4.51, dark #e06c75 5.32) with no background, and the test fails on any token rule that sets one.
1 parent 40c0d2a commit 53c1039

4 files changed

Lines changed: 64 additions & 38 deletions

File tree

‎docs/WEB.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ extension, a stylesheet or a build-time edit of the browser build's own copy of
100100

101101
| Piece | Where | What it does, and what it depends on |
102102
| --- | --- | --- |
103-
| Colour themes | `web/theme/levelcode-web-theme/` (staged as a built-in extension by `build-web.mjs`) | "LevelCode Web Dark" / "LevelCode Web Light": neutral chrome with the editor one step lighter, hairline borders, one violet accent. They carry the `agentsPanel.*` keys the floating cards use. Follows the system (`window.autoDetectColorScheme` + the two preferred-theme settings). |
103+
| Colour themes | `web/theme/levelcode-web-theme/` (staged as a built-in extension by `build-web.mjs`) | "LevelCode Web Dark" / "LevelCode Web Light": neutral chrome with the editor one step lighter, hairline borders, one violet accent. They carry the `agentsPanel.*` keys the floating cards use. Follows the system (`window.autoDetectColorScheme` + the two preferred-theme settings). The syntax colours are One Dark's and One Light's with every one under 4.5:1 on the editor moved in lightness only, hue kept (the light constants darkened with them, so numbers stay apart from class names); `theme.test.js` holds every syntax colour to 4.5:1. The editor paints no token *backgrounds*, so none is set (`invalid` is red text). |
104104
| First paint | `initialColorTheme` in `web/lib/config.mjs`, picked by `pickInitialTheme` in `web/main.js` | The colours painted for the half second before the theme extension has loaded, so a dark visitor does not see a light flash. |
105105
| Default theme | `withoutConfigurationDefaults` in `web/lib/extensions.mjs` | An extension's `configurationDefaults` **outrank** the embedder's. `levelcode-themes` pins its own default theme, so the staged copy of it has that key removed (its two themes stay selectable). The desktop source is untouched. |
106106
| Panels, activity bar | `workbench.experimental.modernUI`, `workbench.activityBar.location: top`, `workbench.layoutControl.type: toggles` | Code-OSS's own switches. `modernUI` is **experimental**: if a bump renames or drops it the editor is still fine, only plainer (`e2e-look.mjs` still passes its theme and layout checks; its screenshots are the thing to look at). |
@@ -324,7 +324,7 @@ transaction, so both end with the same key.
324324

325325
| Check | Command | What it proves |
326326
| --- | --- | --- |
327-
| Unit gate | `./scripts/test-extensions.sh` | Includes `web/test/unit/*.test.js`: the shims against Node, `host.js` on both sides, `main.js`'s functions (address check, delivery, the shared secret store, `?signin=1`), the manifest, the copy rewrites, the chat skin (what it inserts and that every rule is scoped), the themes (required colours, contrast of the text pairs, the page's defaults naming themes that exist) and the layout commands. Runs in CI on every PR (`test.yml`). Also run on Linux / Node 18 in a container with the network refused. |
327+
| Unit gate | `./scripts/test-extensions.sh` | Includes `web/test/unit/*.test.js`: the shims against Node, `host.js` on both sides, `main.js`'s functions (address check, delivery, the shared secret store, `?signin=1`), the manifest, the copy rewrites, the chat skin (what it inserts and that every rule is scoped), the themes (required colours, contrast of the text pairs, every syntax colour at 4.5:1 on the editor, the page's defaults naming themes that exist) and the layout commands. Runs in CI on every PR (`test.yml`). Also run on Linux / Node 18 in a container with the network refused. |
328328
| End to end | `node web/test/e2e.mjs --dist dist-web --stub-port <port the build used>` | Real headless Chrome: boot from static files, the extension activates in the worker, sign in through the real callback page, agent tool calls (write / list / search / read / edit) against the scratch workspace, Quick Open, Keep and Undo, a reload that keeps the session and files. |
329329
| The look | `node web/test/e2e-look.mjs --dist dist-web --stub-port <port>` | The skinned chat (greeting, starters, composer in the reading column, the rail at 1440 px and the dialog at 900 px), light and dark following the system with this build's own colours (also in the chat's webview), no Welcome page, no second chat, source control / run / extensions not offered, the layout switch both ways, and the docked layout docked again after a reload. Release builds only. |
330330
| Same-tab sign-in | `node web/test/e2e-signin-tab.mjs --dist dist-web --stub-port <port>` | No second window; verifier survives the reload; no repeat; no sign-in for a signed-in editor; a second tab uses the same session; a stranded result is delivered, an old one dropped. |

‎web/test/unit/theme.test.js‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
*
44
* A theme is data, so what can go wrong is quiet: a colour that is missing and falls back to a default that
55
* does not belong, text that cannot be read, a default that names a theme the page does not have, or a pinned
6-
* default somewhere else that outranks ours. These tests read the files the build ships.
6+
* default somewhere else that outranks ours. These tests read the files the build ships. "Read" includes the code:
7+
* every syntax colour is held to the same 4.5:1 as the workbench's text (a review found comments at 2.6–2.8:1).
78
*--------------------------------------------------------------------------------------------*/
89
// @ts-check
910
'use strict';
@@ -75,6 +76,33 @@ function over(top, ground) {
7576
}
7677
});
7778

79+
await test('code can be read: every syntax colour meets WCAG AA (4.5:1) on the editor, and none counts on a token background', () => {
80+
for (const t of Object.values(themes)) {
81+
const editor = t.json.colors['editor.background'];
82+
const low = [];
83+
const painted = [];
84+
let checked = 0;
85+
const check = (where, fg) => {
86+
if (fg === undefined) { return; }
87+
assert.ok(/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(fg), `${t.label}: ${where} = ${fg}`);
88+
const c = contrast(over(fg, editor), editor);
89+
checked++;
90+
if (c < 4.5) { low.push(`${where} ${fg} ${c.toFixed(2)}`); }
91+
};
92+
for (const rule of t.json.tokenColors) {
93+
const where = [].concat(rule.scope || '(default)').join(', ');
94+
check(where, rule.settings.foreground);
95+
// The editor paints no token backgrounds ("Token background colors are currently not supported"): a
96+
// foreground chosen for one is drawn on the editor's ground instead — white on white in a light theme.
97+
if (rule.settings.background !== undefined) { painted.push(where); }
98+
}
99+
for (const [k, v] of Object.entries(t.json.semanticTokenColors || {})) { check('semantic ' + k, typeof v === 'string' ? v : v.foreground); }
100+
assert.ok(checked > 20, `${t.label}: only ${checked} syntax colours were checked`);
101+
assert.deepStrictEqual(low, [], `${t.label}: syntax colours below 4.5:1 on ${editor}`);
102+
assert.deepStrictEqual(painted, [], `${t.label}: token backgrounds the editor does not paint`);
103+
}
104+
});
105+
78106
await test('the editor is one step apart from the chrome, in the right direction for each theme', () => {
79107
const d = themes['LevelCode Web Dark'].json.colors;
80108
const l = themes['LevelCode Web Light'].json.colors;

‎web/theme/levelcode-web-theme/themes/web-dark.json‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@
247247
"punctuation.definition.comment"
248248
],
249249
"settings": {
250-
"foreground": "#5c6370",
250+
"foreground": "#7b8493",
251251
"fontStyle": "italic"
252252
}
253253
},
@@ -474,8 +474,7 @@
474474
"invalid.illegal"
475475
],
476476
"settings": {
477-
"foreground": "#ffffff",
478-
"background": "#e06c75"
477+
"foreground": "#e06c75"
479478
}
480479
},
481480
{
@@ -541,7 +540,7 @@
541540
"markup.quote"
542541
],
543542
"settings": {
544-
"foreground": "#5c6370",
543+
"foreground": "#7b8493",
545544
"fontStyle": "italic"
546545
}
547546
},
@@ -551,7 +550,7 @@
551550
"comment.block.documentation"
552551
],
553552
"settings": {
554-
"foreground": "#5c6370"
553+
"foreground": "#7b8493"
555554
}
556555
},
557556
{

‎web/theme/levelcode-web-theme/themes/web-light.json‎

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@
247247
"punctuation.definition.comment"
248248
],
249249
"settings": {
250-
"foreground": "#a0a1a7",
250+
"foreground": "#75767e",
251251
"fontStyle": "italic"
252252
}
253253
},
@@ -257,7 +257,7 @@
257257
"punctuation.definition.string"
258258
],
259259
"settings": {
260-
"foreground": "#50a14f"
260+
"foreground": "#428541"
261261
}
262262
},
263263
{
@@ -266,7 +266,7 @@
266266
"constant.character.escape"
267267
],
268268
"settings": {
269-
"foreground": "#0184bc"
269+
"foreground": "#017eb4"
270270
}
271271
},
272272
{
@@ -277,7 +277,7 @@
277277
"constant.other"
278278
],
279279
"settings": {
280-
"foreground": "#986801"
280+
"foreground": "#765101"
281281
}
282282
},
283283
{
@@ -287,7 +287,7 @@
287287
"constant.language.undefined"
288288
],
289289
"settings": {
290-
"foreground": "#986801"
290+
"foreground": "#765101"
291291
}
292292
},
293293
{
@@ -298,7 +298,7 @@
298298
"entity.name.variable"
299299
],
300300
"settings": {
301-
"foreground": "#e45649"
301+
"foreground": "#df3425"
302302
}
303303
},
304304
{
@@ -317,7 +317,7 @@
317317
"variable.language.special"
318318
],
319319
"settings": {
320-
"foreground": "#e45649",
320+
"foreground": "#df3425",
321321
"fontStyle": "italic"
322322
}
323323
},
@@ -329,7 +329,7 @@
329329
"support.type.property-name"
330330
],
331331
"settings": {
332-
"foreground": "#e45649"
332+
"foreground": "#df3425"
333333
}
334334
},
335335
{
@@ -387,7 +387,7 @@
387387
"meta.function-call.method"
388388
],
389389
"settings": {
390-
"foreground": "#4078f2"
390+
"foreground": "#326ef1"
391391
}
392392
},
393393
{
@@ -398,15 +398,15 @@
398398
"support.class"
399399
],
400400
"settings": {
401-
"foreground": "#c18401"
401+
"foreground": "#9f6d01"
402402
}
403403
},
404404
{
405405
"scope": [
406406
"entity.other.inherited-class"
407407
],
408408
"settings": {
409-
"foreground": "#50a14f"
409+
"foreground": "#428541"
410410
}
411411
},
412412
{
@@ -415,15 +415,15 @@
415415
"punctuation.definition.tag"
416416
],
417417
"settings": {
418-
"foreground": "#e45649"
418+
"foreground": "#df3425"
419419
}
420420
},
421421
{
422422
"scope": [
423423
"entity.other.attribute-name"
424424
],
425425
"settings": {
426-
"foreground": "#986801"
426+
"foreground": "#765101"
427427
}
428428
},
429429
{
@@ -433,7 +433,7 @@
433433
"support.type.builtin"
434434
],
435435
"settings": {
436-
"foreground": "#0184bc"
436+
"foreground": "#017eb4"
437437
}
438438
},
439439
{
@@ -464,7 +464,7 @@
464464
"entity.name.function.decorator"
465465
],
466466
"settings": {
467-
"foreground": "#4078f2"
467+
"foreground": "#326ef1"
468468
}
469469
},
470470
{
@@ -473,8 +473,7 @@
473473
"invalid.illegal"
474474
],
475475
"settings": {
476-
"foreground": "#ffffff",
477-
"background": "#e45649"
476+
"foreground": "#df3425"
478477
}
479478
},
480479
{
@@ -484,7 +483,7 @@
484483
"punctuation.definition.heading.markdown"
485484
],
486485
"settings": {
487-
"foreground": "#e45649",
486+
"foreground": "#df3425",
488487
"fontStyle": "bold"
489488
}
490489
},
@@ -493,7 +492,7 @@
493492
"markup.bold"
494493
],
495494
"settings": {
496-
"foreground": "#986801",
495+
"foreground": "#765101",
497496
"fontStyle": "bold"
498497
}
499498
},
@@ -513,7 +512,7 @@
513512
"markup.raw.block"
514513
],
515514
"settings": {
516-
"foreground": "#50a14f"
515+
"foreground": "#428541"
517516
}
518517
},
519518
{
@@ -522,7 +521,7 @@
522521
"markup.underline.link.image"
523522
],
524523
"settings": {
525-
"foreground": "#4078f2",
524+
"foreground": "#326ef1",
526525
"fontStyle": "underline"
527526
}
528527
},
@@ -531,7 +530,7 @@
531530
"markup.quote"
532531
],
533532
"settings": {
534-
"foreground": "#a0a1a7",
533+
"foreground": "#75767e",
535534
"fontStyle": "italic"
536535
}
537536
},
@@ -540,7 +539,7 @@
540539
"support.type.property-name.json"
541540
],
542541
"settings": {
543-
"foreground": "#e45649"
542+
"foreground": "#df3425"
544543
}
545544
},
546545
{
@@ -549,18 +548,18 @@
549548
"source.css support.type.property-name"
550549
],
551550
"settings": {
552-
"foreground": "#c18401"
551+
"foreground": "#9f6d01"
553552
}
554553
}
555554
],
556555
"semanticTokenColors": {
557556
"parameter": "#383a42",
558-
"property": "#e45649",
559-
"variable.defaultLibrary": "#0184bc",
560-
"function.defaultLibrary": "#4078f2",
561-
"class": "#c18401",
562-
"enum": "#c18401",
563-
"interface": "#c18401",
557+
"property": "#df3425",
558+
"variable.defaultLibrary": "#017eb4",
559+
"function.defaultLibrary": "#326ef1",
560+
"class": "#9f6d01",
561+
"enum": "#9f6d01",
562+
"interface": "#9f6d01",
564563
"namespace": "#383a42"
565564
}
566565
}

0 commit comments

Comments
 (0)