brand-intent 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/parser/brandParser.ts +3 -33
- package/parser/purposeParser.ts +10 -1
- package/parser/storyParser.ts +8 -2
- package/vscode-extension/syntaxes/brand.tmLanguage.json +2 -10
- package/vscode-extension/syntaxes/purpose.tmLanguage.json +2 -2
- package/vscode-extension/syntaxes/story.tmLanguage.json +2 -2
package/package.json
CHANGED
package/parser/brandParser.ts
CHANGED
|
@@ -57,20 +57,6 @@ export interface ParsedBrand {
|
|
|
57
57
|
locale: string;
|
|
58
58
|
/** Named font primitives: name → font definition. First entry is the brand default. */
|
|
59
59
|
fonts?: Record<string, ParsedFontDef>;
|
|
60
|
-
/** Token source type: 'figma' | 'inline' */
|
|
61
|
-
tokensSource: 'figma' | 'inline';
|
|
62
|
-
/** Figma file key (only when tokensSource === 'figma') */
|
|
63
|
-
figmaFileKey?: string;
|
|
64
|
-
/** Figma collection name (only when tokensSource === 'figma') */
|
|
65
|
-
figmaCollection?: string;
|
|
66
|
-
/** Path to the runtime tokens file (relative to /src) */
|
|
67
|
-
tokensFile: string;
|
|
68
|
-
/** Path to app icon asset (relative to /public), used for PWA icon */
|
|
69
|
-
appIcon?: string;
|
|
70
|
-
/** Path to favicon asset (relative to /public), used for browser tab icon */
|
|
71
|
-
favicon?: string;
|
|
72
|
-
/** Path to OG image asset (relative to /public), used for social sharing previews */
|
|
73
|
-
ogImage?: string;
|
|
74
60
|
/** Core brand color primitives: name → hex (e.g. primary, secondary, accent1, accent2, white).
|
|
75
61
|
* Always present when any colors are declared — grouped-syntax brands get a flat projection
|
|
76
62
|
* generated from all entries, so legacy consumers keep working unchanged. */
|
|
@@ -103,11 +89,11 @@ export interface ParsedBrand {
|
|
|
103
89
|
themePairs?: Record<string, ThemePair>;
|
|
104
90
|
/** Non-fatal color findings: rule violations, rejected family pairs, reach config hints. */
|
|
105
91
|
colorWarnings?: string[];
|
|
106
|
-
/** Inline themes: theme name → slot name → resolved hex color
|
|
92
|
+
/** Inline themes: theme name → slot name → resolved hex color */
|
|
107
93
|
themes?: Record<string, Record<string, string>>;
|
|
108
94
|
/** Print annotations from inline # comments: slot name → annotation text */
|
|
109
95
|
themeDescriptions?: Record<string, string>;
|
|
110
|
-
/** Spacing scale
|
|
96
|
+
/** Spacing scale */
|
|
111
97
|
spacing?: { unit: string; xs: number; s: number; m: number; l: number; xl: number };
|
|
112
98
|
/** Divider style definitions: name → divider config */
|
|
113
99
|
dividers?: Record<string, ParsedDividerDef>;
|
|
@@ -450,10 +436,7 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
450
436
|
return { slot, value, line: lineNum };
|
|
451
437
|
};
|
|
452
438
|
|
|
453
|
-
const result: Partial<ParsedBrand> = {
|
|
454
|
-
tokensFile: 'tokens.json',
|
|
455
|
-
tokensSource: 'figma',
|
|
456
|
-
};
|
|
439
|
+
const result: Partial<ParsedBrand> = {};
|
|
457
440
|
|
|
458
441
|
type ActiveBlock = 'none' | 'brand-colors' | 'state-colors' | 'color-bounds' | 'color-adherence' | 'color-rules' | 'tones' | 'levels' | 'color-families' | 'theme' | 'spacing' | 'divider' | 'typography' | 'font' | 'logo' | 'photo' | 'video' | 'illustration' | 'label' | 'cta' | 'icons' | 'counter' | 'framing' | 'camera' | 'photo-look' | 'windows';
|
|
459
442
|
let activeBlock: ActiveBlock = 'none';
|
|
@@ -1164,19 +1147,6 @@ export function parseBrandFile(content: string, fileName: string): ParsedBrand {
|
|
|
1164
1147
|
case 'name': result.name = value; break;
|
|
1165
1148
|
case 'language': result.language = value; break;
|
|
1166
1149
|
case 'locale': result.locale = value; break;
|
|
1167
|
-
case 'tokens':
|
|
1168
|
-
if (value === 'figma' || value === 'inline') {
|
|
1169
|
-
result.tokensSource = value;
|
|
1170
|
-
} else {
|
|
1171
|
-
console.warn(`${fileName}:${lineNum}: unknown tokens source "${value}", defaulting to "figma"`);
|
|
1172
|
-
}
|
|
1173
|
-
break;
|
|
1174
|
-
case 'figma-file-key': result.figmaFileKey = value; break;
|
|
1175
|
-
case 'figma-collection': result.figmaCollection = value; break;
|
|
1176
|
-
case 'tokens-file': result.tokensFile = value; break;
|
|
1177
|
-
case 'app-icon': result.appIcon = value; break;
|
|
1178
|
-
case 'favicon': result.favicon = value; break;
|
|
1179
|
-
case 'og-image': result.ogImage = value; break;
|
|
1180
1150
|
case 'spacing':
|
|
1181
1151
|
activeBlock = 'spacing';
|
|
1182
1152
|
break;
|
package/parser/purposeParser.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface ParsedPurpose {
|
|
|
37
37
|
defaultPhoto?: string;
|
|
38
38
|
/** Default video asset ID — pre-selects this video on page creation. */
|
|
39
39
|
defaultVideo?: string;
|
|
40
|
+
/** Illustration ids a new page picks from; a .story page's `illustration:` overrides them. */
|
|
41
|
+
illustrations?: string[];
|
|
40
42
|
/** When false, hides Take Photo / Upload buttons for this purpose. Default: true. */
|
|
41
43
|
camera?: boolean;
|
|
42
44
|
/** `camera: front | rear` — overrides the brand's camera facing for this purpose. */
|
|
@@ -170,6 +172,7 @@ export function parsePurposeFile(content: string, fileName: string): ParsedPurpo
|
|
|
170
172
|
let defaultLogo: string | undefined;
|
|
171
173
|
let defaultPhoto: string | undefined;
|
|
172
174
|
let defaultVideo: string | undefined;
|
|
175
|
+
let illustrations: string[] | undefined;
|
|
173
176
|
let camera: boolean | undefined;
|
|
174
177
|
let cameraFacing: 'front' | 'rear' | undefined;
|
|
175
178
|
let framing: string | undefined;
|
|
@@ -422,6 +425,12 @@ export function parsePurposeFile(content: string, fileName: string): ParsedPurpo
|
|
|
422
425
|
continue;
|
|
423
426
|
}
|
|
424
427
|
|
|
428
|
+
// Illustrations a new page picks from
|
|
429
|
+
if (line.startsWith('illustration:')) {
|
|
430
|
+
illustrations = line.slice(13).split(',').map(id => id.trim()).filter(Boolean);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
425
434
|
// Camera control
|
|
426
435
|
if (line.startsWith('camera:')) {
|
|
427
436
|
const value = line.slice(7).trim();
|
|
@@ -788,7 +797,7 @@ export function parsePurposeFile(content: string, fileName: string): ParsedPurpo
|
|
|
788
797
|
|
|
789
798
|
const parsedExamples = resolveExamples(examples, slots, legacySamplesLine, fileName, warnings);
|
|
790
799
|
|
|
791
|
-
return { id, name, description, preferredCompositions: compositions, slots, density, palette, scope, context, voice, defaultLogo, defaultPhoto, defaultVideo, camera, cameraFacing, framing, goal, analyze, defaultIcon, bgPrompt, examples: parsedExamples, warnings };
|
|
800
|
+
return { id, name, description, preferredCompositions: compositions, slots, density, palette, scope, context, voice, defaultLogo, defaultPhoto, defaultVideo, illustrations, camera, cameraFacing, framing, goal, analyze, defaultIcon, bgPrompt, examples: parsedExamples, warnings };
|
|
792
801
|
}
|
|
793
802
|
|
|
794
803
|
/** Validate `example` blocks against the declared slots, or build examples from deprecated slot
|
package/parser/storyParser.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface ParsedStoryPage {
|
|
|
38
38
|
purpose: string;
|
|
39
39
|
/** Composition id; undefined = the implementation picks one of the purpose's compositions. */
|
|
40
40
|
composition?: string;
|
|
41
|
+
/** Illustration ids; implementations pick one per story date, the same one every time for that date. */
|
|
42
|
+
illustrations?: string[];
|
|
41
43
|
/** Page topic: what this page adds to the story. */
|
|
42
44
|
topic?: string;
|
|
43
45
|
/** Fixed slot texts, may contain `{variable}`s. A slot the page doesn't name is open. */
|
|
@@ -190,9 +192,10 @@ export function parseStoryFile(content: string, fileName: string): ParsedStory {
|
|
|
190
192
|
// Page lines (2-space)
|
|
191
193
|
if (currentPage) {
|
|
192
194
|
if (indent !== 2) throw new Error(`${fileName}:${lineNum}: unexpected indent in page (page keys use 2 spaces)`);
|
|
193
|
-
const read = readKey(line, lineNum, currentPage, ['purpose', 'composition'], true);
|
|
195
|
+
const read = readKey(line, lineNum, currentPage, ['purpose', 'composition', 'illustration'], true);
|
|
194
196
|
if (read && 'multi' in read) { multiKey = read.multi; multiIndent = 4; }
|
|
195
197
|
else if (read?.key === 'purpose') currentPage.purpose = read.value;
|
|
198
|
+
else if (read?.key === 'illustration') currentPage.illustrations = read.value.split(',').map(id => id.trim()).filter(Boolean);
|
|
196
199
|
else if (read) currentPage.composition = read.value;
|
|
197
200
|
continue;
|
|
198
201
|
}
|
|
@@ -278,7 +281,7 @@ export function parseStoryFile(content: string, fileName: string): ParsedStory {
|
|
|
278
281
|
* empty when the story fits. Variables count at their longest form for `maxLength`. */
|
|
279
282
|
export function checkStory(
|
|
280
283
|
story: ParsedStory,
|
|
281
|
-
brand: { purposes: ParsedPurpose[]; compositions: string[]; formats: string[] },
|
|
284
|
+
brand: { purposes: ParsedPurpose[]; compositions: string[]; formats: string[]; illustrations?: string[] },
|
|
282
285
|
fileName: string,
|
|
283
286
|
): string[] {
|
|
284
287
|
const problems: string[] = [];
|
|
@@ -308,6 +311,9 @@ export function checkStory(
|
|
|
308
311
|
if (!brand.compositions.includes(page.composition)) say(`${where}: composition "${page.composition}" does not exist`);
|
|
309
312
|
else if (!purpose.preferredCompositions.includes(page.composition)) say(`${where}: composition "${page.composition}" is not one of purpose "${purpose.id}"'s compositions`);
|
|
310
313
|
}
|
|
314
|
+
for (const id of page.illustrations ?? []) {
|
|
315
|
+
if (brand.illustrations && !brand.illustrations.includes(id)) say(`${where}: illustration "${id}" does not exist`);
|
|
316
|
+
}
|
|
311
317
|
checkTexts(where, purpose, page.texts);
|
|
312
318
|
});
|
|
313
319
|
story.examples.forEach((ex, i) => {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
{ "include": "#comment" },
|
|
7
7
|
{ "include": "#id-field" },
|
|
8
8
|
{ "include": "#name-field" },
|
|
9
|
-
{ "include": "#tokens-enum" },
|
|
10
9
|
{ "include": "#brand-colors-header" },
|
|
11
10
|
{ "include": "#state-colors-header" },
|
|
12
11
|
{ "include": "#color-bounds-header" },
|
|
@@ -80,13 +79,6 @@
|
|
|
80
79
|
"2": { "name": "string.unquoted.brand-intent-brand" }
|
|
81
80
|
}
|
|
82
81
|
},
|
|
83
|
-
"tokens-enum": {
|
|
84
|
-
"match": "^(tokens:)\\s+(figma|inline)",
|
|
85
|
-
"captures": {
|
|
86
|
-
"1": { "name": "keyword.control.brand-intent-brand" },
|
|
87
|
-
"2": { "name": "entity.name.tag.brand-intent-brand" }
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
82
|
"brand-colors-header": {
|
|
91
83
|
"match": "^(brand-colors)\\s*$",
|
|
92
84
|
"captures": {
|
|
@@ -535,7 +527,7 @@
|
|
|
535
527
|
}
|
|
536
528
|
},
|
|
537
529
|
"string-field": {
|
|
538
|
-
"match": "^(language|locale|
|
|
530
|
+
"match": "^(language|locale|spacing):?\\s*(.+)?",
|
|
539
531
|
"captures": {
|
|
540
532
|
"1": { "name": "support.type.property-name.brand-intent-brand" },
|
|
541
533
|
"2": { "name": "string.unquoted.brand-intent-brand" }
|
|
@@ -543,7 +535,7 @@
|
|
|
543
535
|
},
|
|
544
536
|
"keyword-partial": {
|
|
545
537
|
"comment": "Fallback: color the keyword token on incomplete lines (while typing)",
|
|
546
|
-
"match": "^(id:|name:|language:|locale:|
|
|
538
|
+
"match": "^(id:|name:|language:|locale:|spacing:|label:|cta:|color-mode:|color-strategy:|color-adherence:|brand-colors|state-colors|color-bounds|color-rules|color-families|theme|divider|font|typography|logo|photo|video|illustration|icons|counter|framing|windows)(?=\\s|$)",
|
|
547
539
|
"captures": {
|
|
548
540
|
"1": { "name": "keyword.control.brand-intent-brand" }
|
|
549
541
|
}
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
]
|
|
111
111
|
},
|
|
112
112
|
"asset-field": {
|
|
113
|
-
"match": "^(logo:|photo:|video:)\\s+([\\w-]+)",
|
|
113
|
+
"match": "^(logo:|photo:|video:|illustration:)\\s+([\\w, -]+)",
|
|
114
114
|
"captures": {
|
|
115
115
|
"1": { "name": "keyword.control.brand-intent-purpose" },
|
|
116
116
|
"2": { "name": "entity.name.type.brand-intent-purpose" }
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
"comment": "Fallback: color the keyword token on incomplete lines (while typing)",
|
|
266
266
|
"patterns": [
|
|
267
267
|
{
|
|
268
|
-
"match": "^(id:|name:|compositions:|density:|defaultIcon:|scope|voice|palette:|logo:|photo:|video:|camera:|framing:|goal:|analyze:|bg-prompt:|context|slot|example)(?=\\s|$)",
|
|
268
|
+
"match": "^(id:|name:|compositions:|density:|defaultIcon:|scope|voice|palette:|logo:|photo:|video:|illustration:|camera:|framing:|goal:|analyze:|bg-prompt:|context|slot|example)(?=\\s|$)",
|
|
269
269
|
"captures": {
|
|
270
270
|
"1": { "name": "keyword.control.brand-intent-purpose" }
|
|
271
271
|
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
"reference": {
|
|
40
|
-
"match": "^\\s+(purpose|composition)(:)\\s+([\\w
|
|
40
|
+
"match": "^\\s+(purpose|composition|illustration)(:)\\s+([\\w:, -]+)",
|
|
41
41
|
"captures": {
|
|
42
42
|
"1": { "name": "support.type.property-name.brand-intent-story" },
|
|
43
43
|
"2": { "name": "punctuation.separator.brand-intent-story" },
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
|
-
"match": "^\\s+(purpose|composition|topic|primary|secondary|detail|meta|cta|label)(?=:|\\s|$)",
|
|
69
|
+
"match": "^\\s+(purpose|composition|illustration|topic|primary|secondary|detail|meta|cta|label)(?=:|\\s|$)",
|
|
70
70
|
"captures": {
|
|
71
71
|
"1": { "name": "support.type.property-name.brand-intent-story" }
|
|
72
72
|
}
|