implements PLN-0072

This commit is contained in:
bQUARKz 2026-05-06 15:24:21 +01:00
parent cab0e92e60
commit 9d75f2f0e2
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
9 changed files with 330 additions and 7 deletions

View File

@ -1,5 +1,5 @@
{"type":"meta","next_id":{"DSC":35,"AGD":38,"DEC":35,"PLN":75,"LSN":47,"CLSN":1}}
{"type":"discussion","id":"DSC-0034","status":"in_progress","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0072","file":"PLN-0072-frontend-semantic-host-projection-contract.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0073","file":"PLN-0073-lsp-semantic-host-projection-transport.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0074","file":"PLN-0074-vscode-semantic-host-projection-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0034","status":"in_progress","ticket":"frontend-semantic-host-projection-flexibility","title":"Frontend semantic vocabulary flexibility and declarative host projection","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","semantics","vscode","host-projection","lsp"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0034","file":"DEC-0034-frontend-semantic-host-projection-flexibility.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0072","file":"PLN-0072-frontend-semantic-host-projection-contract.md","status":"done","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0073","file":"PLN-0073-lsp-semantic-host-projection-transport.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]},{"id":"PLN-0074","file":"PLN-0074-vscode-semantic-host-projection-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0034"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0033","status":"in_progress","ticket":"frontend-visual-theme-spec-and-css-retirement","title":"Frontend visual theme spec and retirement of host-consumed semantic CSS","created_at":"2026-05-06","updated_at":"2026-05-06","tags":["compiler","compiler-general","frontend","presentation","theming","studio","vscode","lsp","pbs"],"agendas":[{"id":"AGD-0036","file":"AGD-0036-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06"}],"decisions":[{"id":"DEC-0033","file":"DEC-0033-frontend-visual-theme-spec-and-css-retirement.md","status":"accepted","created_at":"2026-05-06","updated_at":"2026-05-06","ref_agenda":"AGD-0036"}],"plans":[{"id":"PLN-0068","file":"PLN-0068-frontend-visual-contract-and-pbs-migration.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0069","file":"PLN-0069-lsp-visual-theme-transport-and-selection.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0070","file":"PLN-0070-studio-visual-theme-adapter-and-css-retirement.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]},{"id":"PLN-0071","file":"PLN-0071-vscode-dynamic-theme-translation-adapter.md","status":"open","created_at":"2026-05-06","updated_at":"2026-05-06","ref_decisions":["DEC-0033"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0032","status":"in_progress","ticket":"studio-new-lsp-api-and-v1-boundary","title":"Novo boundary entre lsp-api, lsp-v1 e a extensao VS Code","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","lsp","vscode","protocol","api","boundary"],"agendas":[{"id":"AGD-0035","file":"AGD-0035-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0032","file":"DEC-0032-studio-new-lsp-api-and-v1-boundary.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0035"}],"plans":[{"id":"PLN-0065","file":"PLN-0065-lsp-boundary-and-module-scaffolding.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0066","file":"PLN-0066-project-scoped-lsp-server-lifecycle-in-studio.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]},{"id":"PLN-0067","file":"PLN-0067-compiler-backed-dumb-lsp-server-baseline.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0032"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0031","status":"in_progress","ticket":"studio-editor-workspace-cleanup","title":"Limpeza completa do Workspace Editor do Studio e remoção dos acoplamentos legados","created_at":"2026-05-05","updated_at":"2026-05-05","tags":["studio","editor","cleanup","vfs","lsp","migration"],"agendas":[{"id":"AGD-0034","file":"AGD-0034-vscode-editor-migration-feasibility.md","status":"accepted","created_at":"2026-05-05","updated_at":"2026-05-05"}],"decisions":[{"id":"DEC-0031","file":"DEC-0031-studio-editor-workspace-cleanup.md","status":"in_progress","created_at":"2026-05-05","updated_at":"2026-05-05","ref_agenda":"AGD-0034"}],"plans":[{"id":"PLN-0062","file":"PLN-0062-shell-session-and-state-cleanup-after-editor-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0063","file":"PLN-0063-legacy-lsp-and-vfs-module-removal-with-runtime-preservation.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]},{"id":"PLN-0064","file":"PLN-0064-normative-test-and-lesson-cleanup-for-editor-stack-removal.md","status":"done","created_at":"2026-05-05","updated_at":"2026-05-05","ref_decisions":["DEC-0031"]}],"lessons":[]}

View File

@ -2,9 +2,9 @@
id: PLN-0072
ticket: frontend-semantic-host-projection-flexibility
title: Frontend Semantic Host Projection Contract
status: open
status: done
created: 2026-05-06
completed:
completed: 2026-05-06
tags: [compiler, compiler-general, frontend, semantics, host-projection, vscode, pbs]
---

View File

@ -17,6 +17,7 @@ public class PBSDefinitions {
.semanticPresentation(new FrontendSemanticPresentationSpec(
PbsSemanticKind.semanticKeys(),
List.of(PBSFrontendThemes.DEFAULT_PBS),
"pbs-default"))
"pbs-default",
List.of(PBSFrontendHostProjections.VSCODE)))
.build();
}

View File

@ -0,0 +1,97 @@
package p.studio.compiler;
import p.studio.compiler.models.FrontendHostProjectionEntrySpec;
import p.studio.compiler.models.FrontendHostProjectionSpec;
import java.util.Arrays;
import static p.studio.compiler.pbs.PbsSemanticKind.BUILTIN_TYPE;
import static p.studio.compiler.pbs.PbsSemanticKind.CALLBACK;
import static p.studio.compiler.pbs.PbsSemanticKind.COMMENT;
import static p.studio.compiler.pbs.PbsSemanticKind.CONST;
import static p.studio.compiler.pbs.PbsSemanticKind.CONSTRUCTOR;
import static p.studio.compiler.pbs.PbsSemanticKind.CONTRACT;
import static p.studio.compiler.pbs.PbsSemanticKind.ENUM;
import static p.studio.compiler.pbs.PbsSemanticKind.ERROR;
import static p.studio.compiler.pbs.PbsSemanticKind.FUNCTION;
import static p.studio.compiler.pbs.PbsSemanticKind.GLOBAL;
import static p.studio.compiler.pbs.PbsSemanticKind.HOST;
import static p.studio.compiler.pbs.PbsSemanticKind.IDENTIFIER;
import static p.studio.compiler.pbs.PbsSemanticKind.IMPLEMENTS;
import static p.studio.compiler.pbs.PbsSemanticKind.KEYWORD;
import static p.studio.compiler.pbs.PbsSemanticKind.LIFECYCLE;
import static p.studio.compiler.pbs.PbsSemanticKind.LITERAL;
import static p.studio.compiler.pbs.PbsSemanticKind.METHOD;
import static p.studio.compiler.pbs.PbsSemanticKind.NUMBER;
import static p.studio.compiler.pbs.PbsSemanticKind.OPERATOR;
import static p.studio.compiler.pbs.PbsSemanticKind.PUNCTUATION;
import static p.studio.compiler.pbs.PbsSemanticKind.SERVICE;
import static p.studio.compiler.pbs.PbsSemanticKind.STRING;
import static p.studio.compiler.pbs.PbsSemanticKind.STRUCT;
public final class PBSFrontendHostProjections {
public static final String VSCODE_HOST_ID = "vscode";
public static final FrontendHostProjectionSpec VSCODE = new FrontendHostProjectionSpec(
VSCODE_HOST_ID,
Arrays.stream(p.studio.compiler.pbs.PbsSemanticKind.values())
.map(PBSFrontendHostProjections::mapVscodeProjection)
.toList());
private PBSFrontendHostProjections() {
}
private static FrontendHostProjectionEntrySpec mapVscodeProjection(final p.studio.compiler.pbs.PbsSemanticKind kind) {
return switch (kind) {
case COMMENT -> projection(COMMENT.semanticKey(), "comment");
case STRING -> projection(STRING.semanticKey(), "string");
case NUMBER -> projection(NUMBER.semanticKey(), "number");
case LITERAL -> projection(LITERAL.semanticKey(), "enumMember", "variable");
case LIFECYCLE -> projection(LIFECYCLE.semanticKey(), "function", "variable", "async");
case KEYWORD -> projection(KEYWORD.semanticKey(), "keyword");
case OPERATOR -> projection(OPERATOR.semanticKey(), "operator", "keyword");
case PUNCTUATION -> projection(PUNCTUATION.semanticKey(), "operator", "keyword");
case FUNCTION -> projection(FUNCTION.semanticKey(), "function");
case METHOD -> projection(METHOD.semanticKey(), "method", "function");
case CONSTRUCTOR -> projection(CONSTRUCTOR.semanticKey(), "class", "type", "declaration");
case STRUCT -> projection(STRUCT.semanticKey(), "struct", "type");
case CONTRACT -> projection(CONTRACT.semanticKey(), "interface", "type");
case HOST -> projection(HOST.semanticKey(), "class", "type", "defaultLibrary");
case BUILTIN_TYPE -> projection(BUILTIN_TYPE.semanticKey(), "type", "variable", "defaultLibrary");
case SERVICE -> projection(SERVICE.semanticKey(), "class", "type", "defaultLibrary");
case ERROR -> projection(ERROR.semanticKey(), "enum", "type");
case ENUM -> projection(ENUM.semanticKey(), "enum", "type");
case CALLBACK -> projection(CALLBACK.semanticKey(), "interface", "type");
case GLOBAL -> projection(GLOBAL.semanticKey(), "variable", "variable", "readonly");
case CONST -> projection(CONST.semanticKey(), "variable", "variable", "readonly");
case IMPLEMENTS -> projection(IMPLEMENTS.semanticKey(), "keyword");
case IDENTIFIER -> projection(IDENTIFIER.semanticKey(), "variable");
};
}
private static FrontendHostProjectionEntrySpec projection(
final String semanticKey,
final String hostTokenType) {
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, java.util.List.of(), hostTokenType, java.util.List.of());
}
private static FrontendHostProjectionEntrySpec projection(
final String semanticKey,
final String hostTokenType,
final String fallbackTokenType) {
return new FrontendHostProjectionEntrySpec(semanticKey, hostTokenType, java.util.List.of(), fallbackTokenType, java.util.List.of());
}
private static FrontendHostProjectionEntrySpec projection(
final String semanticKey,
final String hostTokenType,
final String fallbackTokenType,
final String modifier) {
return new FrontendHostProjectionEntrySpec(
semanticKey,
hostTokenType,
java.util.List.of(modifier),
fallbackTokenType,
java.util.List.of(modifier));
}
}

View File

@ -1,6 +1,7 @@
package p.studio.compiler.pbs;
import org.junit.jupiter.api.Test;
import p.studio.compiler.PBSFrontendHostProjections;
import p.studio.compiler.PBSDefinitions;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -21,6 +22,7 @@ final class PbsSemanticPresentationContractTest {
assertEquals("#d9e2ec", presentation.defaultTheme().editorPalette().baseForeground());
assertEquals("#569cd6", presentation.defaultTheme().requireTokenStyle("pbs-keyword").foreground());
assertTrue(presentation.defaultTheme().requireTokenStyle("pbs-comment").italic());
assertEquals(PBSFrontendHostProjections.VSCODE_HOST_ID, presentation.requireHostProjection("vscode").hostId());
}
@Test
@ -32,4 +34,19 @@ final class PbsSemanticPresentationContractTest {
PbsSemanticKind.semanticKeys(),
defaultTheme.tokenStyles().stream().map(tokenStyle -> tokenStyle.semanticKey()).toList());
}
@Test
void shouldCoverTheWholePbsSemanticVocabularyInTheVscodeProjection() {
final var presentation = PBSDefinitions.PBS.getSemanticPresentation();
final var vscodeProjection = presentation.requireHostProjection(PBSFrontendHostProjections.VSCODE_HOST_ID);
assertEquals(
PbsSemanticKind.semanticKeys(),
vscodeProjection.tokenProjections().stream().map(tokenProjection -> tokenProjection.semanticKey()).toList());
assertEquals("keyword", vscodeProjection.requireProjection(PbsSemanticKind.KEYWORD.semanticKey()).hostTokenType());
assertEquals("type", vscodeProjection.requireProjection(PbsSemanticKind.STRUCT.semanticKey()).fallbackTokenType());
assertEquals(
java.util.List.of("readonly"),
vscodeProjection.requireProjection(PbsSemanticKind.GLOBAL.semanticKey()).hostTokenModifiers());
}
}

View File

@ -0,0 +1,38 @@
package p.studio.compiler.models;
import java.util.List;
import java.util.Objects;
public record FrontendHostProjectionEntrySpec(
String semanticKey,
String hostTokenType,
List<String> hostTokenModifiers,
String fallbackTokenType,
List<String> fallbackTokenModifiers) {
public FrontendHostProjectionEntrySpec {
semanticKey = requireText(semanticKey, "semanticKey");
hostTokenType = requireText(hostTokenType, "hostTokenType");
hostTokenModifiers = normalizeList(hostTokenModifiers, "hostTokenModifiers");
fallbackTokenType = requireText(fallbackTokenType, "fallbackTokenType");
fallbackTokenModifiers = normalizeList(fallbackTokenModifiers, "fallbackTokenModifiers");
}
private static List<String> normalizeList(
final List<String> values,
final String field) {
return List.copyOf(Objects.requireNonNull(values, field).stream()
.map(value -> requireText(value, field + " entry"))
.toList());
}
private static String requireText(
final String value,
final String field) {
final String candidate = Objects.requireNonNull(value, field).trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException(field + " must not be blank");
}
return candidate;
}
}

View File

@ -0,0 +1,48 @@
package p.studio.compiler.models;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public record FrontendHostProjectionSpec(
String hostId,
List<FrontendHostProjectionEntrySpec> tokenProjections) {
public FrontendHostProjectionSpec {
hostId = requireText(hostId, "hostId");
tokenProjections = List.copyOf(Objects.requireNonNull(tokenProjections, "tokenProjections"));
if (tokenProjections.isEmpty()) {
throw new IllegalArgumentException("tokenProjections must not be empty");
}
final Map<String, FrontendHostProjectionEntrySpec> bySemanticKey = new LinkedHashMap<>();
for (final FrontendHostProjectionEntrySpec tokenProjection : tokenProjections) {
final FrontendHostProjectionEntrySpec previous = bySemanticKey.put(tokenProjection.semanticKey(), tokenProjection);
if (previous != null) {
throw new IllegalArgumentException("duplicate host projection for semantic key: " + tokenProjection.semanticKey());
}
}
}
public Optional<FrontendHostProjectionEntrySpec> projectionFor(final String semanticKey) {
return tokenProjections.stream()
.filter(tokenProjection -> tokenProjection.semanticKey().equals(semanticKey))
.findFirst();
}
public FrontendHostProjectionEntrySpec requireProjection(final String semanticKey) {
return projectionFor(semanticKey)
.orElseThrow(() -> new IllegalArgumentException("missing host projection for semantic key: " + semanticKey));
}
private static String requireText(
final String value,
final String field) {
final String candidate = Objects.requireNonNull(value, field).trim();
if (candidate.isEmpty()) {
throw new IllegalArgumentException(field + " must not be blank");
}
return candidate;
}
}

View File

@ -6,12 +6,21 @@ import java.util.Objects;
public record FrontendSemanticPresentationSpec(
List<String> semanticKeys,
List<FrontendVisualThemeSpec> themes,
String defaultThemeId) {
String defaultThemeId,
List<FrontendHostProjectionSpec> hostProjections) {
public FrontendSemanticPresentationSpec(
final List<String> semanticKeys,
final List<FrontendVisualThemeSpec> themes,
final String defaultThemeId) {
this(semanticKeys, themes, defaultThemeId, List.of());
}
public FrontendSemanticPresentationSpec {
semanticKeys = List.copyOf(Objects.requireNonNull(semanticKeys, "semanticKeys"));
themes = List.copyOf(Objects.requireNonNull(themes, "themes"));
defaultThemeId = Objects.requireNonNull(defaultThemeId, "defaultThemeId").trim();
hostProjections = List.copyOf(Objects.requireNonNull(hostProjections, "hostProjections"));
if (semanticKeys.isEmpty() && themes.isEmpty()) {
if (!defaultThemeId.isEmpty()) {
throw new IllegalArgumentException("defaultThemeId must be blank when no themes are declared");
@ -25,10 +34,27 @@ public record FrontendSemanticPresentationSpec(
throw new IllegalArgumentException("defaultThemeId must reference one of the declared themes");
}
}
final List<String> normalizedSemanticKeys = semanticKeys.stream()
.map(key -> requireText(key, "semanticKeys entry"))
.toList();
final var hostIds = new java.util.LinkedHashSet<String>();
for (final FrontendHostProjectionSpec hostProjection : hostProjections) {
if (!hostIds.add(hostProjection.hostId())) {
throw new IllegalArgumentException("duplicate host projection for hostId: " + hostProjection.hostId());
}
for (final FrontendHostProjectionEntrySpec tokenProjection : hostProjection.tokenProjections()) {
if (!normalizedSemanticKeys.contains(tokenProjection.semanticKey())) {
throw new IllegalArgumentException(
"host projection for hostId '" + hostProjection.hostId() + "' references unknown semantic key: "
+ tokenProjection.semanticKey());
}
}
}
semanticKeys = normalizedSemanticKeys;
}
public static FrontendSemanticPresentationSpec empty() {
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "");
return new FrontendSemanticPresentationSpec(List.of(), List.of(), "", List.of());
}
public FrontendVisualThemeSpec defaultTheme() {
@ -42,7 +68,18 @@ public record FrontendSemanticPresentationSpec(
}
public boolean isEmpty() {
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty();
return semanticKeys.isEmpty() && themes.isEmpty() && defaultThemeId.isEmpty() && hostProjections.isEmpty();
}
public java.util.Optional<FrontendHostProjectionSpec> hostProjection(final String hostId) {
return hostProjections.stream()
.filter(hostProjection -> hostProjection.hostId().equals(requireText(hostId, "hostId")))
.findFirst();
}
public FrontendHostProjectionSpec requireHostProjection(final String hostId) {
return hostProjection(hostId)
.orElseThrow(() -> new IllegalArgumentException("missing host projection for hostId: " + hostId));
}
private static String requireText(

View File

@ -0,0 +1,85 @@
package p.studio.compiler.models;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
final class FrontendSemanticPresentationSpecTest {
@Test
void shouldExposeHostProjectionByHostId() {
final var presentation = new FrontendSemanticPresentationSpec(
List.of("demo-key"),
List.of(new FrontendVisualThemeSpec(
"demo-theme",
"Demo Theme",
new FrontendEditorPaletteSpec("#111111", "#eeeeee", "#333333", "#777777", "#111111", "#222222", "#eeeeee"),
List.of(new FrontendTokenStyleSpec("demo-key", "#eeeeee", false, false, false)))),
"demo-theme",
List.of(new FrontendHostProjectionSpec(
"vscode",
List.of(new FrontendHostProjectionEntrySpec(
"demo-key",
"keyword",
List.of("declaration"),
"variable",
List.of())))));
final var vscodeProjection = presentation.requireHostProjection("vscode").requireProjection("demo-key");
assertEquals("keyword", vscodeProjection.hostTokenType());
assertEquals(List.of("declaration"), vscodeProjection.hostTokenModifiers());
assertEquals("variable", vscodeProjection.fallbackTokenType());
}
@Test
void shouldRejectDuplicateHostIds() {
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
List.of("demo-key"),
List.of(new FrontendVisualThemeSpec(
"demo-theme",
"Demo Theme",
new FrontendEditorPaletteSpec("#111111", "#eeeeee", "#333333", "#777777", "#111111", "#222222", "#eeeeee"),
List.of(new FrontendTokenStyleSpec("demo-key", "#eeeeee", false, false, false)))),
"demo-theme",
List.of(
new FrontendHostProjectionSpec(
"vscode",
List.of(new FrontendHostProjectionEntrySpec(
"demo-key",
"keyword",
List.of(),
"variable",
List.of()))),
new FrontendHostProjectionSpec(
"vscode",
List.of(new FrontendHostProjectionEntrySpec(
"demo-key",
"type",
List.of(),
"variable",
List.of()))))));
}
@Test
void shouldRejectProjectionForUnknownSemanticKey() {
assertThrows(IllegalArgumentException.class, () -> new FrontendSemanticPresentationSpec(
List.of("demo-key"),
List.of(new FrontendVisualThemeSpec(
"demo-theme",
"Demo Theme",
new FrontendEditorPaletteSpec("#111111", "#eeeeee", "#333333", "#777777", "#111111", "#222222", "#eeeeee"),
List.of(new FrontendTokenStyleSpec("demo-key", "#eeeeee", false, false, false)))),
"demo-theme",
List.of(new FrontendHostProjectionSpec(
"vscode",
List.of(new FrontendHostProjectionEntrySpec(
"unknown-key",
"keyword",
List.of(),
"variable",
List.of()))))));
}
}