@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.120 → 2.0.0-next.121
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/bundle/openbridge-webcomponents.bundle.js +22 -14
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +1 -1
- package/dist/integration-systems/integration-button/integration-button.css.js +20 -13
- package/dist/integration-systems/integration-button/integration-button.css.js.map +1 -1
- package/dist/integration-systems/integration-button/integration-button.d.ts +1 -1
- package/dist/integration-systems/integration-button/integration-button.js +1 -1
- package/dist/integration-systems/integration-button/integration-button.js.map +1 -1
- package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.css.js +1 -0
- package/dist/integration-systems/integration-dropdown-button/integration-dropdown-button.css.js.map +1 -1
- package/package.json +4 -2
- package/script/agent-docs/emitters.ts +189 -0
- package/script/agent-docs/frontmatter.ts +118 -0
- package/script/check-slot-event-docs.ts +2 -2
- package/script/sync-agent-docs.test.ts +298 -0
- package/script/sync-agent-docs.ts +156 -0
- package/src/icons/AGENTS.md +6 -0
- package/src/integration-systems/integration-button/integration-button.css +20 -13
- package/src/integration-systems/integration-button/integration-button.stories.ts +70 -44
- package/src/integration-systems/integration-button/integration-button.ts +2 -2
- package/src/integration-systems/integration-dropdown-button/integration-dropdown-button.css +1 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import {readFileSync} from 'node:fs';
|
|
2
|
+
import {describe, expect, it} from 'vitest';
|
|
3
|
+
import {parseAgentDoc, type AgentDoc} from './agent-docs/frontmatter.js';
|
|
4
|
+
import {
|
|
5
|
+
ROUTING_MARKER,
|
|
6
|
+
renderClaudeMd,
|
|
7
|
+
renderCopilotInstructions,
|
|
8
|
+
renderCursorRule,
|
|
9
|
+
renderInstructionsFile,
|
|
10
|
+
renderRoutingTable,
|
|
11
|
+
replaceMarkedBlock,
|
|
12
|
+
rewriteSiblingLinks,
|
|
13
|
+
} from './agent-docs/emitters.js';
|
|
14
|
+
|
|
15
|
+
const VALID = `---
|
|
16
|
+
name: a11y
|
|
17
|
+
description: Accessibility (WCAG 2.1 AA) — keyboard nav, ARIA, focus
|
|
18
|
+
globs:
|
|
19
|
+
- packages/openbridge-webcomponents/src/components/**
|
|
20
|
+
- packages/openbridge-webcomponents/src/automation/**
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Accessibility Instructions
|
|
24
|
+
|
|
25
|
+
Body text.
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
describe('parseAgentDoc', () => {
|
|
29
|
+
it('parses name, description and globs', () => {
|
|
30
|
+
const doc = parseAgentDoc(VALID, 'docs/agents/a11y.md');
|
|
31
|
+
expect(doc.name).toBe('a11y');
|
|
32
|
+
expect(doc.description).toBe(
|
|
33
|
+
'Accessibility (WCAG 2.1 AA) — keyboard nav, ARIA, focus'
|
|
34
|
+
);
|
|
35
|
+
expect(doc.globs).toEqual([
|
|
36
|
+
'packages/openbridge-webcomponents/src/components/**',
|
|
37
|
+
'packages/openbridge-webcomponents/src/automation/**',
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns the body without the frontmatter', () => {
|
|
42
|
+
const doc = parseAgentDoc(VALID, 'docs/agents/a11y.md');
|
|
43
|
+
expect(doc.body).toBe('# Accessibility Instructions\n\nBody text.\n');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('strips single quotes used to escape leading "!"', () => {
|
|
47
|
+
const raw = `---
|
|
48
|
+
name: jsdoc
|
|
49
|
+
description: JSDoc rules
|
|
50
|
+
globs:
|
|
51
|
+
- packages/openbridge-webcomponents/src/**/*.ts
|
|
52
|
+
- '!packages/openbridge-webcomponents/src/icons/**'
|
|
53
|
+
---
|
|
54
|
+
body
|
|
55
|
+
`;
|
|
56
|
+
expect(parseAgentDoc(raw, 'docs/agents/jsdoc.md').globs[1]).toBe(
|
|
57
|
+
'!packages/openbridge-webcomponents/src/icons/**'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('throws when name does not match the filename', () => {
|
|
62
|
+
expect(() => parseAgentDoc(VALID, 'docs/agents/wrong.md')).toThrow(
|
|
63
|
+
/name "a11y" does not match filename "wrong"/
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('throws on an unsupported key', () => {
|
|
68
|
+
const raw = `---
|
|
69
|
+
name: x
|
|
70
|
+
description: d
|
|
71
|
+
globs:
|
|
72
|
+
- a/**
|
|
73
|
+
alwaysApply: true
|
|
74
|
+
---
|
|
75
|
+
body
|
|
76
|
+
`;
|
|
77
|
+
expect(() => parseAgentDoc(raw, 'docs/agents/x.md')).toThrow(
|
|
78
|
+
/unsupported key "alwaysApply" on line 6/
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('throws when globs is empty', () => {
|
|
83
|
+
const raw = `---
|
|
84
|
+
name: x
|
|
85
|
+
description: d
|
|
86
|
+
globs:
|
|
87
|
+
---
|
|
88
|
+
body
|
|
89
|
+
`;
|
|
90
|
+
expect(() => parseAgentDoc(raw, 'docs/agents/x.md')).toThrow(
|
|
91
|
+
/"globs" must list at least one pattern/
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('throws when there is no frontmatter', () => {
|
|
96
|
+
expect(() =>
|
|
97
|
+
parseAgentDoc('# no frontmatter\n', 'docs/agents/x.md')
|
|
98
|
+
).toThrow(/must start with a "---" frontmatter block/);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const DOC: AgentDoc = {
|
|
103
|
+
name: 'a11y',
|
|
104
|
+
description: 'Accessibility (WCAG 2.1 AA)',
|
|
105
|
+
globs: ['packages/openbridge-webcomponents/src/components/**'],
|
|
106
|
+
body: '# Accessibility\n\nBody.\n',
|
|
107
|
+
sourcePath: 'docs/agents/a11y.md',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
describe('renderInstructionsFile', () => {
|
|
111
|
+
it('writes applyTo from globs and keeps the body verbatim', () => {
|
|
112
|
+
const out = renderInstructionsFile(DOC);
|
|
113
|
+
expect(out).toContain(
|
|
114
|
+
'applyTo: "packages/openbridge-webcomponents/src/components/**"'
|
|
115
|
+
);
|
|
116
|
+
expect(out).toContain('# Accessibility\n\nBody.\n');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('joins multiple globs with commas', () => {
|
|
120
|
+
const out = renderInstructionsFile({...DOC, globs: ['a/**', 'b/**']});
|
|
121
|
+
expect(out).toContain('applyTo: "a/**,b/**"');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('includes a do-not-edit banner naming the source and the command', () => {
|
|
125
|
+
const out = renderInstructionsFile(DOC);
|
|
126
|
+
expect(out).toContain('GENERATED FILE — DO NOT EDIT');
|
|
127
|
+
expect(out).toContain('docs/agents/a11y.md');
|
|
128
|
+
expect(out).toContain('npm run agents:sync');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('puts the frontmatter first so Copilot can read applyTo', () => {
|
|
132
|
+
expect(renderInstructionsFile(DOC).startsWith('---\napplyTo:')).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('renderRoutingTable', () => {
|
|
137
|
+
it('emits one row per doc with description and globs', () => {
|
|
138
|
+
const table = renderRoutingTable([DOC]);
|
|
139
|
+
expect(table).toContain('[a11y](docs/agents/a11y.md)');
|
|
140
|
+
expect(table).toContain('Accessibility (WCAG 2.1 AA)');
|
|
141
|
+
expect(table).toContain(
|
|
142
|
+
'`packages/openbridge-webcomponents/src/components/**`'
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('sorts rows by name regardless of input order', () => {
|
|
147
|
+
const z: AgentDoc = {
|
|
148
|
+
...DOC,
|
|
149
|
+
name: 'zebra',
|
|
150
|
+
sourcePath: 'docs/agents/zebra.md',
|
|
151
|
+
};
|
|
152
|
+
const table = renderRoutingTable([z, DOC]);
|
|
153
|
+
expect(table.indexOf('[a11y]')).toBeLessThan(table.indexOf('[zebra]'));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('renderCopilotInstructions', () => {
|
|
158
|
+
it('points at AGENTS.md and carries the routing table but no bodies', () => {
|
|
159
|
+
const out = renderCopilotInstructions([DOC]);
|
|
160
|
+
expect(out).toContain('AGENTS.md');
|
|
161
|
+
expect(out).toContain('[a11y](docs/agents/a11y.md)');
|
|
162
|
+
expect(out).not.toContain('Body.');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('replaceMarkedBlock', () => {
|
|
167
|
+
it('replaces only the content between the markers', () => {
|
|
168
|
+
const content = [
|
|
169
|
+
'before',
|
|
170
|
+
`<!-- ${ROUTING_MARKER}:start -->`,
|
|
171
|
+
'OLD',
|
|
172
|
+
`<!-- ${ROUTING_MARKER}:end -->`,
|
|
173
|
+
'after',
|
|
174
|
+
].join('\n');
|
|
175
|
+
const out = replaceMarkedBlock(content, ROUTING_MARKER, 'NEW');
|
|
176
|
+
expect(out).toBe(
|
|
177
|
+
[
|
|
178
|
+
'before',
|
|
179
|
+
`<!-- ${ROUTING_MARKER}:start -->`,
|
|
180
|
+
'NEW',
|
|
181
|
+
`<!-- ${ROUTING_MARKER}:end -->`,
|
|
182
|
+
'after',
|
|
183
|
+
].join('\n')
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('throws when the markers are missing', () => {
|
|
188
|
+
expect(() => replaceMarkedBlock('no markers', ROUTING_MARKER, 'x')).toThrow(
|
|
189
|
+
/markers .* not found/
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('renderClaudeMd', () => {
|
|
195
|
+
it('points at AGENTS.md and docs/agents', () => {
|
|
196
|
+
const out = renderClaudeMd();
|
|
197
|
+
expect(out).toContain('AGENTS.md');
|
|
198
|
+
expect(out).toContain('docs/agents/');
|
|
199
|
+
expect(out).toContain('GENERATED FILE — DO NOT EDIT');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('carries no rules of its own — it is a pointer, not a source', () => {
|
|
203
|
+
const out = renderClaudeMd();
|
|
204
|
+
expect(out).toContain('intentionally adds nothing of its own');
|
|
205
|
+
// No numbered rule list: team rules belong in AGENTS.md, path-scoped rules
|
|
206
|
+
// in docs/agents/. Neither is tool-specific.
|
|
207
|
+
expect(out).not.toMatch(/^\d+\. \*\*/m);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('CLI failure semantics', () => {
|
|
212
|
+
const cli = 'script/sync-agent-docs.ts';
|
|
213
|
+
|
|
214
|
+
it('write mode never exits non-zero, so `prepare` cannot break npm ci', () => {
|
|
215
|
+
const src = readFileSync(cli, 'utf8');
|
|
216
|
+
expect(src).toContain('if (CHECK) process.exit(1);');
|
|
217
|
+
expect(src).not.toMatch(/^\s*process\.exit\(1\);\s*$/m);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('only skips negative globs in the resolution check', () => {
|
|
221
|
+
const src = readFileSync(cli, 'utf8');
|
|
222
|
+
expect(src).toContain("if (g.startsWith('!')) continue;");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('renderCursorRule', () => {
|
|
227
|
+
it('emits globs + alwaysApply:false — Cursor\'s "Apply to Specific Files" type', () => {
|
|
228
|
+
const out = renderCursorRule(DOC);
|
|
229
|
+
expect(out.startsWith('---\n')).toBe(true);
|
|
230
|
+
expect(out).toContain(
|
|
231
|
+
'globs: packages/openbridge-webcomponents/src/components/**'
|
|
232
|
+
);
|
|
233
|
+
expect(out).toContain('alwaysApply: false');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('omits description, which would select "Apply Intelligently" instead', () => {
|
|
237
|
+
expect(renderCursorRule(DOC)).not.toContain('description:');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('joins multiple globs with commas', () => {
|
|
241
|
+
expect(renderCursorRule({...DOC, globs: ['a/**', 'b/**']})).toContain(
|
|
242
|
+
'globs: a/**,b/**'
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('drops negative globs, which Cursor does not document', () => {
|
|
247
|
+
const out = renderCursorRule({...DOC, globs: ['a/**', '!b/**', 'c/**']});
|
|
248
|
+
expect(out).toContain('globs: a/**,c/**');
|
|
249
|
+
expect(out).not.toContain('!b/**');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('keeps the body verbatim and carries the do-not-edit banner', () => {
|
|
253
|
+
const out = renderCursorRule(DOC);
|
|
254
|
+
expect(out).toContain('# Accessibility\n\nBody.\n');
|
|
255
|
+
expect(out).toContain('GENERATED FILE — DO NOT EDIT');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('rewriteSiblingLinks', () => {
|
|
260
|
+
const siblings = new Set(['jsdoc', 'a11y', 'css-postcss']);
|
|
261
|
+
|
|
262
|
+
it('repoints bare sibling links at the canonical doc', () => {
|
|
263
|
+
expect(rewriteSiblingLinks('see [`jsdoc.md`](jsdoc.md).', siblings)).toBe(
|
|
264
|
+
'see [`jsdoc.md`](../../docs/agents/jsdoc.md).'
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('preserves anchors', () => {
|
|
269
|
+
expect(rewriteSiblingLinks('[x](a11y.md#section-2)', siblings)).toBe(
|
|
270
|
+
'[x](../../docs/agents/a11y.md#section-2)'
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('leaves ../../ links alone — they already resolve from every adapter dir', () => {
|
|
275
|
+
const link = '[g](../../IMPLEMENTATION_GUIDELINES.md#-postcss)';
|
|
276
|
+
expect(rewriteSiblingLinks(link, siblings)).toBe(link);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('leaves external and absolute links alone', () => {
|
|
280
|
+
const body = '[a](https://example.com/x.md) [b](/abs/y.md)';
|
|
281
|
+
expect(rewriteSiblingLinks(body, siblings)).toBe(body);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('leaves bare links that are not canonical docs alone', () => {
|
|
285
|
+
const link = '[r](README.md)';
|
|
286
|
+
expect(rewriteSiblingLinks(link, siblings)).toBe(link);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('is applied by both adapter renderers', () => {
|
|
290
|
+
const doc = {...DOC, body: 'see [`jsdoc.md`](jsdoc.md).\n'};
|
|
291
|
+
expect(renderInstructionsFile(doc, siblings)).toContain(
|
|
292
|
+
'](../../docs/agents/jsdoc.md)'
|
|
293
|
+
);
|
|
294
|
+
expect(renderCursorRule(doc, siblings)).toContain(
|
|
295
|
+
'](../../docs/agents/jsdoc.md)'
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {globSync} from 'glob';
|
|
4
|
+
import * as prettier from 'prettier';
|
|
5
|
+
import {parseAgentDoc, type AgentDoc} from './agent-docs/frontmatter.js';
|
|
6
|
+
import {
|
|
7
|
+
ROUTING_MARKER,
|
|
8
|
+
renderClaudeMd,
|
|
9
|
+
renderCopilotInstructions,
|
|
10
|
+
renderCursorRule,
|
|
11
|
+
renderInstructionsFile,
|
|
12
|
+
renderRoutingTable,
|
|
13
|
+
replaceMarkedBlock,
|
|
14
|
+
} from './agent-docs/emitters.js';
|
|
15
|
+
|
|
16
|
+
const ROOT = path.resolve(import.meta.dirname, '../../..');
|
|
17
|
+
const AGENTS_DIR = path.join(ROOT, 'docs/agents');
|
|
18
|
+
const INSTRUCTIONS_DIR = path.join(ROOT, '.github/instructions');
|
|
19
|
+
const CURSOR_RULES_DIR = path.join(ROOT, '.cursor/rules');
|
|
20
|
+
const CHECK = process.argv.includes('--check');
|
|
21
|
+
|
|
22
|
+
const problems: string[] = [];
|
|
23
|
+
const writes = new Map<string, string>();
|
|
24
|
+
|
|
25
|
+
function plan(absPath: string, content: string): void {
|
|
26
|
+
writes.set(absPath, content);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function loadDocs(): AgentDoc[] {
|
|
30
|
+
if (!fs.existsSync(AGENTS_DIR)) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`docs/agents does not exist at ${AGENTS_DIR} — nothing to sync`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return fs
|
|
36
|
+
.readdirSync(AGENTS_DIR)
|
|
37
|
+
.filter((f) => f.endsWith('.md'))
|
|
38
|
+
.sort()
|
|
39
|
+
.map((f) => {
|
|
40
|
+
const rel = path.posix.join('docs/agents', f);
|
|
41
|
+
return parseAgentDoc(
|
|
42
|
+
fs.readFileSync(path.join(AGENTS_DIR, f), 'utf8'),
|
|
43
|
+
rel
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Check 1 — every positive glob must match at least one real path. */
|
|
49
|
+
function checkGlobs(docs: AgentDoc[]): void {
|
|
50
|
+
for (const doc of docs) {
|
|
51
|
+
for (const g of doc.globs) {
|
|
52
|
+
if (g.startsWith('!')) continue;
|
|
53
|
+
if (globSync(g, {cwd: ROOT, dot: true}).length === 0) {
|
|
54
|
+
problems.push(`${doc.sourcePath}: glob matches nothing → ${g}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Check 4 — no generated adapter without a canonical source. */
|
|
61
|
+
function checkOrphans(docs: AgentDoc[]): void {
|
|
62
|
+
const adapters: [string, string, string][] = [
|
|
63
|
+
[INSTRUCTIONS_DIR, '.instructions.md', '.github/instructions'],
|
|
64
|
+
[CURSOR_RULES_DIR, '.mdc', '.cursor/rules'],
|
|
65
|
+
];
|
|
66
|
+
for (const [dir, ext, rel] of adapters) {
|
|
67
|
+
if (!fs.existsSync(dir)) continue;
|
|
68
|
+
const expected = new Set(docs.map((d) => `${d.name}${ext}`));
|
|
69
|
+
for (const f of fs.readdirSync(dir)) {
|
|
70
|
+
if (f.endsWith(ext) && !expected.has(f)) {
|
|
71
|
+
problems.push(
|
|
72
|
+
`${rel}/${f}: no matching docs/agents/ source (delete it or add the source)`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const docs = loadDocs();
|
|
80
|
+
checkGlobs(docs);
|
|
81
|
+
checkOrphans(docs);
|
|
82
|
+
|
|
83
|
+
const routable = docs;
|
|
84
|
+
const docNames = new Set(docs.map((d) => d.name));
|
|
85
|
+
|
|
86
|
+
for (const doc of routable) {
|
|
87
|
+
plan(
|
|
88
|
+
path.join(INSTRUCTIONS_DIR, `${doc.name}.instructions.md`),
|
|
89
|
+
renderInstructionsFile(doc, docNames)
|
|
90
|
+
);
|
|
91
|
+
plan(
|
|
92
|
+
path.join(CURSOR_RULES_DIR, `${doc.name}.mdc`),
|
|
93
|
+
renderCursorRule(doc, docNames)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
plan(
|
|
97
|
+
path.join(ROOT, '.github/copilot-instructions.md'),
|
|
98
|
+
renderCopilotInstructions(routable)
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const agentsPath = path.join(ROOT, 'AGENTS.md');
|
|
102
|
+
plan(
|
|
103
|
+
agentsPath,
|
|
104
|
+
replaceMarkedBlock(
|
|
105
|
+
fs.readFileSync(agentsPath, 'utf8'),
|
|
106
|
+
ROUTING_MARKER,
|
|
107
|
+
renderRoutingTable(routable)
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
plan(path.join(ROOT, 'CLAUDE.md'), renderClaudeMd());
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Runs generated Markdown through Prettier before writing or comparing.
|
|
115
|
+
*
|
|
116
|
+
* Without this the generator's compact tables would differ from what
|
|
117
|
+
* `npm run format:check` (and the lint-staged `.md` hook) demand, so every
|
|
118
|
+
* sync would leave the repo format-dirty and CI would fail. Formatting here
|
|
119
|
+
* makes generator output and Prettier output the same artefact by
|
|
120
|
+
* construction, and keeps `--check` comparing like with like.
|
|
121
|
+
*/
|
|
122
|
+
async function format(content: string, absPath: string): Promise<string> {
|
|
123
|
+
const config = await prettier.resolveConfig(absPath);
|
|
124
|
+
return prettier.format(content, {
|
|
125
|
+
...config,
|
|
126
|
+
filepath: absPath,
|
|
127
|
+
parser: 'markdown',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const [absPath, raw] of writes) {
|
|
132
|
+
const rel = path.relative(ROOT, absPath);
|
|
133
|
+
const content = await format(raw, absPath);
|
|
134
|
+
const current = fs.existsSync(absPath)
|
|
135
|
+
? fs.readFileSync(absPath, 'utf8')
|
|
136
|
+
: null;
|
|
137
|
+
if (current === content) continue;
|
|
138
|
+
if (CHECK) {
|
|
139
|
+
problems.push(`${rel}: out of date — run \`npm run agents:sync\``);
|
|
140
|
+
} else {
|
|
141
|
+
fs.mkdirSync(path.dirname(absPath), {recursive: true});
|
|
142
|
+
fs.writeFileSync(absPath, content);
|
|
143
|
+
console.log(`${current === null ? 'created' : 'updated'} ${rel}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (problems.length > 0) {
|
|
148
|
+
console.error(`\nagent-docs: ${problems.length} problem(s)`);
|
|
149
|
+
for (const p of problems) console.error(` ${CHECK ? '✗' : 'warning:'} ${p}`);
|
|
150
|
+
// Write mode reports but never fails. `agents:sync` runs from the root
|
|
151
|
+
// `prepare` script, so a stale glob or a missing source must not be able to
|
|
152
|
+
// break `npm ci` for everyone. `lint:agents` is the gate; it runs in CI after
|
|
153
|
+
// the build steps, when any generated paths actually exist.
|
|
154
|
+
if (CHECK) process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
console.log(CHECK ? 'agent-docs: up to date' : 'agent-docs: sync complete');
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# src/icons — generated, do not edit
|
|
2
|
+
|
|
3
|
+
Every file here is generated from Figma by `npm run download:icons`. Edits are
|
|
4
|
+
discarded on the next run. See [`docs/agents/generated-code.md`](../../../../docs/agents/generated-code.md).
|
|
5
|
+
|
|
6
|
+
Note `src/manual-icon/` is the opposite: hand-written, edit it normally.
|
|
@@ -27,9 +27,13 @@
|
|
|
27
27
|
.touch-target {
|
|
28
28
|
@mixin style style=integration-normal visibleWrapperClass=.content-container;
|
|
29
29
|
@mixin font-body;
|
|
30
|
+
--leading-icon-size: var(
|
|
31
|
+
--app-components-integration-system-navigation-item-horizontal-icon-size
|
|
32
|
+
);
|
|
30
33
|
anchor-name: --touch-target;
|
|
31
34
|
display: flex;
|
|
32
35
|
align-items: center;
|
|
36
|
+
text-align: left;
|
|
33
37
|
gap: 4px;
|
|
34
38
|
flex: 1;
|
|
35
39
|
min-height: var(
|
|
@@ -169,20 +173,23 @@
|
|
|
169
173
|
color: var(--integration-on-normal-neutral-color);
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
--app-components-integration-system-navigation-item-horizontal-icon-size
|
|
175
|
-
);
|
|
176
|
-
height: var(
|
|
177
|
-
--app-components-integration-system-navigation-item-horizontal-icon-size
|
|
176
|
+
&.has-status {
|
|
177
|
+
--leading-icon-size: var(
|
|
178
|
+
--app-components-integration-system-navigation-item-horizontal-icon-size-large
|
|
178
179
|
);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.icon.leading {
|
|
183
|
+
width: var(--leading-icon-size);
|
|
184
|
+
height: var(--leading-icon-size);
|
|
185
|
+
min-width: var(--leading-icon-size);
|
|
186
|
+
flex: 0 0 var(--leading-icon-size);
|
|
187
|
+
|
|
188
|
+
& ::slotted(*) {
|
|
189
|
+
display: block;
|
|
190
|
+
width: 100%;
|
|
191
|
+
height: 100%;
|
|
192
|
+
}
|
|
186
193
|
}
|
|
187
194
|
|
|
188
195
|
.icon.trailing {
|
|
@@ -8,6 +8,35 @@ import './integration-button.js';
|
|
|
8
8
|
import {html} from 'lit';
|
|
9
9
|
import '../../icons/icon-placeholder.js';
|
|
10
10
|
|
|
11
|
+
const LONG_STATUS =
|
|
12
|
+
'Very long status for a vessel at sea doing something important';
|
|
13
|
+
|
|
14
|
+
const renderButton = (args: ObcIntegrationButton, statusText: string) => html`
|
|
15
|
+
<obc-integration-button
|
|
16
|
+
style="width: 320px; display: block;"
|
|
17
|
+
.hasLeadingIcon=${args.hasLeadingIcon}
|
|
18
|
+
.hasTrailingIcon=${args.hasTrailingIcon}
|
|
19
|
+
.hasTrailingIcon2=${args.hasTrailingIcon2}
|
|
20
|
+
.hasStatus=${args.hasStatus}
|
|
21
|
+
.readouts=${args.readouts}
|
|
22
|
+
.selected=${args.selected}
|
|
23
|
+
.activated=${args.activated}
|
|
24
|
+
.disabled=${args.disabled}
|
|
25
|
+
.dividerBottom=${args.dividerBottom}
|
|
26
|
+
.dividerRight=${args.dividerRight}
|
|
27
|
+
.variant=${args.variant}
|
|
28
|
+
.type=${args.type}
|
|
29
|
+
>
|
|
30
|
+
<obi-placeholder slot="leading-icon"></obi-placeholder>
|
|
31
|
+
<obi-placeholder slot="trailing-icon"></obi-placeholder>
|
|
32
|
+
<obi-placeholder slot="trailing-icon2"></obi-placeholder>
|
|
33
|
+
<div slot="label">Label</div>
|
|
34
|
+
<div slot="status">${statusText}</div>
|
|
35
|
+
<div slot="info-label">Info Label</div>
|
|
36
|
+
<div slot="info-status">Info Status</div>
|
|
37
|
+
</obc-integration-button>
|
|
38
|
+
`;
|
|
39
|
+
|
|
11
40
|
const meta: Meta<ObcIntegrationButton> = {
|
|
12
41
|
title: 'Integration Systems/Integration Button',
|
|
13
42
|
tags: ['experimental'],
|
|
@@ -30,28 +59,7 @@ const meta: Meta<ObcIntegrationButton> = {
|
|
|
30
59
|
value: 'integration-container-global-color',
|
|
31
60
|
},
|
|
32
61
|
},
|
|
33
|
-
render: (args) =>
|
|
34
|
-
<obc-integration-button
|
|
35
|
-
style="width: 320px; display: block;"
|
|
36
|
-
.hasLeadingIcon=${args.hasLeadingIcon}
|
|
37
|
-
.hasTrailingIcon=${args.hasTrailingIcon}
|
|
38
|
-
.hasTrailingIcon2=${args.hasTrailingIcon2}
|
|
39
|
-
.hasStatus=${args.hasStatus}
|
|
40
|
-
.readouts=${args.readouts}
|
|
41
|
-
.selected=${args.selected}
|
|
42
|
-
.disabled=${args.disabled}
|
|
43
|
-
.variant=${args.variant}
|
|
44
|
-
.type=${args.type}
|
|
45
|
-
>
|
|
46
|
-
<obi-placeholder slot="leading-icon"></obi-placeholder>
|
|
47
|
-
<obi-placeholder slot="trailing-icon"></obi-placeholder>
|
|
48
|
-
<obi-placeholder slot="trailing-icon2"></obi-placeholder>
|
|
49
|
-
<div slot="label">Label</div>
|
|
50
|
-
<div slot="status">Status</div>
|
|
51
|
-
<div slot="info-label">Info Label</div>
|
|
52
|
-
<div slot="info-status">Info Status</div>
|
|
53
|
-
</obc-integration-button>
|
|
54
|
-
`,
|
|
62
|
+
render: (args) => renderButton(args, 'Status'),
|
|
55
63
|
} satisfies Meta<ObcIntegrationButton>;
|
|
56
64
|
export default meta;
|
|
57
65
|
|
|
@@ -61,28 +69,6 @@ export const WithStatus: StoryObj<ObcIntegrationButton> = {
|
|
|
61
69
|
args: {
|
|
62
70
|
hasStatus: true,
|
|
63
71
|
},
|
|
64
|
-
render: (args) => html`
|
|
65
|
-
<obc-integration-button
|
|
66
|
-
style="width: 320px; display: block;"
|
|
67
|
-
.hasLeadingIcon=${args.hasLeadingIcon}
|
|
68
|
-
.hasTrailingIcon=${args.hasTrailingIcon}
|
|
69
|
-
.hasTrailingIcon2=${args.hasTrailingIcon2}
|
|
70
|
-
.readouts=${args.readouts}
|
|
71
|
-
.selected=${args.selected}
|
|
72
|
-
.disabled=${args.disabled}
|
|
73
|
-
.variant=${args.variant}
|
|
74
|
-
.type=${args.type}
|
|
75
|
-
.hasStatus=${args.hasStatus}
|
|
76
|
-
>
|
|
77
|
-
<obi-placeholder slot="leading-icon"></obi-placeholder>
|
|
78
|
-
<obi-placeholder slot="trailing-icon"></obi-placeholder>
|
|
79
|
-
<obi-placeholder slot="trailing-icon2"></obi-placeholder>
|
|
80
|
-
<div slot="label">Label</div>
|
|
81
|
-
<div slot="status">Status</div>
|
|
82
|
-
<div slot="info-label">Info Label</div>
|
|
83
|
-
<div slot="info-status">Info Status</div>
|
|
84
|
-
</obc-integration-button>
|
|
85
|
-
`,
|
|
86
72
|
};
|
|
87
73
|
|
|
88
74
|
export const Selected: StoryObj<ObcIntegrationButton> = {
|
|
@@ -111,9 +97,49 @@ export const Rich: StoryObj<ObcIntegrationButton> = {
|
|
|
111
97
|
},
|
|
112
98
|
};
|
|
113
99
|
|
|
100
|
+
export const RichWithStatus: StoryObj<ObcIntegrationButton> = {
|
|
101
|
+
args: {
|
|
102
|
+
type: IntegrationButtonType.rich,
|
|
103
|
+
hasStatus: true,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
114
107
|
export const Disabled: StoryObj<ObcIntegrationButton> = {
|
|
115
108
|
args: {
|
|
116
109
|
type: IntegrationButtonType.rich,
|
|
117
110
|
disabled: true,
|
|
118
111
|
},
|
|
119
112
|
};
|
|
113
|
+
|
|
114
|
+
export const WithLongStatus: StoryObj<ObcIntegrationButton> = {
|
|
115
|
+
args: {
|
|
116
|
+
hasStatus: true,
|
|
117
|
+
},
|
|
118
|
+
render: (args) => renderButton(args, LONG_STATUS),
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const HugWithLongStatus: StoryObj<ObcIntegrationButton> = {
|
|
122
|
+
args: {
|
|
123
|
+
type: IntegrationButtonType.hug,
|
|
124
|
+
hasStatus: true,
|
|
125
|
+
hasTrailingIcon: false,
|
|
126
|
+
},
|
|
127
|
+
render: (args) => renderButton(args, LONG_STATUS),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const RichWithLongStatus: StoryObj<ObcIntegrationButton> = {
|
|
131
|
+
args: {
|
|
132
|
+
type: IntegrationButtonType.rich,
|
|
133
|
+
hasStatus: true,
|
|
134
|
+
},
|
|
135
|
+
render: (args) => renderButton(args, LONG_STATUS),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const RichWithLongReadoutLabel: StoryObj<ObcIntegrationButton> = {
|
|
139
|
+
args: {
|
|
140
|
+
type: IntegrationButtonType.rich,
|
|
141
|
+
hasStatus: true,
|
|
142
|
+
readouts: [{label: 'Estimated Time of Arrival', value: '12', unit: 'h'}],
|
|
143
|
+
},
|
|
144
|
+
render: (args) => renderButton(args, 'Status'),
|
|
145
|
+
};
|
|
@@ -24,7 +24,7 @@ export enum IntegrationButtonType {
|
|
|
24
24
|
/**
|
|
25
25
|
* `<obc-integration-button>` – A button component for integration systems.
|
|
26
26
|
*
|
|
27
|
-
* @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true)
|
|
27
|
+
* @slot leading-icon - Icon before label (shown when `hasLeadingIcon` is true); rendered at the large icon size when `hasStatus` is true
|
|
28
28
|
* @slot trailing-icon - Icon after label (shown when `hasTrailingIcon` is true)
|
|
29
29
|
* @slot trailing-icon2 - Icon after label (shown when `hasTrailingIcon2` is true)
|
|
30
30
|
* @slot label - Label text
|
|
@@ -76,7 +76,7 @@ export class ObcIntegrationButton extends LitElement {
|
|
|
76
76
|
selected: this.selected,
|
|
77
77
|
activated: this.activated,
|
|
78
78
|
disabled: this.disabled,
|
|
79
|
-
'has-
|
|
79
|
+
'has-status': this.hasStatus,
|
|
80
80
|
['variant-' + this.variant]: true,
|
|
81
81
|
['type-' + this.type]: true,
|
|
82
82
|
};
|