@kerfjs/ui 5.0.0-beta.18 → 5.0.0-beta.21

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.
Files changed (137) hide show
  1. package/README.md +114 -3
  2. package/ai/application-ui-diagnostic-ids-v1.json +92 -0
  3. package/ai/application-ui-profile-sync.cjs +529 -0
  4. package/ai/application-ui-profile.d.mts +1 -0
  5. package/ai/application-ui-profile.d.ts +102 -0
  6. package/ai/application-ui-profile.defaults.json +41 -0
  7. package/ai/application-ui-profile.mjs +626 -0
  8. package/ai/application-ui-profile.schema.json +130 -0
  9. package/ai/catalog-authoring.json +25 -0
  10. package/ai/catalog-authoring.schema.json +61 -0
  11. package/ai/compile-time-contracts-v1.json +101 -0
  12. package/ai/compile-time-contracts-v1.schema.json +49 -0
  13. package/ai/component-catalog-extension-v2.d.ts +7 -0
  14. package/ai/component-catalog-extension-v2.schema.json +7 -0
  15. package/ai/component-catalog-v2-overrides.json +326 -0
  16. package/ai/component-catalog-v2.d.ts +67 -0
  17. package/ai/component-catalog-v2.json +4441 -0
  18. package/ai/component-catalog-v2.schema.json +246 -0
  19. package/ai/component-catalog.json +150 -6
  20. package/ai/public-api-signatures-v1.md +294 -64
  21. package/ai/skill.md +41 -5
  22. package/ai/webawesome-jsx-signatures-v1.md +1 -1
  23. package/analyzer/cli.mjs +46 -0
  24. package/analyzer/index.d.mts +45 -0
  25. package/analyzer/index.mjs +743 -0
  26. package/analyzer/report.schema.json +66 -0
  27. package/dist/browser/pane.js +3 -0
  28. package/dist/browser/sunken-panel.js +3 -0
  29. package/dist/catalog-resources.d.ts +34 -0
  30. package/dist/catalog-resources.js +22 -0
  31. package/dist/catalog-resources.js.map +1 -0
  32. package/dist/catalog.d.ts +21 -3
  33. package/dist/catalog.js +239 -210
  34. package/dist/catalog.js.map +1 -1
  35. package/dist/{chunk-ZK2G3VR5.js → chunk-5VTB2BKP.js} +1 -1
  36. package/dist/chunk-5VTB2BKP.js.map +1 -0
  37. package/dist/{chunk-S3HKPI7I.js → chunk-7KEUJIIC.js} +1 -1
  38. package/dist/chunk-7KEUJIIC.js.map +1 -0
  39. package/dist/{chunk-NVWXKU37.js → chunk-AASLYRGE.js} +1 -1
  40. package/dist/chunk-AASLYRGE.js.map +1 -0
  41. package/dist/{chunk-ENVTNLYC.js → chunk-CN2XNTAM.js} +6 -6
  42. package/dist/chunk-CN2XNTAM.js.map +1 -0
  43. package/dist/{chunk-QIRXYKCE.js → chunk-DSNIYOZ3.js} +3 -2
  44. package/dist/chunk-DSNIYOZ3.js.map +1 -0
  45. package/dist/chunk-JRKSK2HX.js +79 -0
  46. package/dist/chunk-JRKSK2HX.js.map +1 -0
  47. package/dist/{chunk-U5IFGQCD.js → chunk-K57KRAOV.js} +1 -1
  48. package/dist/chunk-K57KRAOV.js.map +1 -0
  49. package/dist/{chunk-ZVI5YST2.js → chunk-M7JBER5U.js} +1 -1
  50. package/dist/chunk-M7JBER5U.js.map +1 -0
  51. package/dist/{chunk-43FUQH35.js → chunk-OHU6CI22.js} +2 -2
  52. package/dist/{chunk-43FUQH35.js.map → chunk-OHU6CI22.js.map} +1 -1
  53. package/dist/{chunk-4TRDV6RL.js → chunk-OZWU5ZPZ.js} +1 -1
  54. package/dist/chunk-OZWU5ZPZ.js.map +1 -0
  55. package/dist/{chunk-JTHUXDN2.js → chunk-RYRIFZCT.js} +3 -3
  56. package/dist/chunk-RYRIFZCT.js.map +1 -0
  57. package/dist/{chunk-6ETZKKAE.js → chunk-VLYV65LK.js} +1 -1
  58. package/dist/chunk-VLYV65LK.js.map +1 -0
  59. package/dist/chunk-W4CXQYSG.js +23 -0
  60. package/dist/chunk-W4CXQYSG.js.map +1 -0
  61. package/dist/{chunk-S3PCZ7NV.js → chunk-W7YD6E4N.js} +1 -1
  62. package/dist/chunk-W7YD6E4N.js.map +1 -0
  63. package/dist/index.d.ts +6 -4
  64. package/dist/index.js +14 -12
  65. package/dist/list-header.d.ts +29 -8
  66. package/dist/list-header.js +1 -1
  67. package/dist/nav-stack.js +1 -1
  68. package/dist/pane.d.ts +49 -0
  69. package/dist/pane.js +4 -0
  70. package/dist/pane.js.map +1 -0
  71. package/dist/panel-header.d.ts +9 -3
  72. package/dist/panel-header.js +1 -1
  73. package/dist/segmented-control.d.ts +6 -6
  74. package/dist/segmented-control.js +1 -1
  75. package/dist/select.d.ts +10 -4
  76. package/dist/select.js +1 -1
  77. package/dist/split-view.js +2 -2
  78. package/dist/state-banner.d.ts +3 -2
  79. package/dist/state-banner.js +1 -1
  80. package/dist/styles/catalog.css +6 -6
  81. package/dist/styles/layout.css +2 -20
  82. package/dist/styles/pane.css +62 -0
  83. package/dist/styles/segmented-control.css +3 -7
  84. package/dist/styles/sunken-panel.css +18 -0
  85. package/dist/sunken-panel.d.ts +16 -0
  86. package/dist/sunken-panel.js +3 -0
  87. package/dist/sunken-panel.js.map +1 -0
  88. package/dist/tab-bar.d.ts +3 -2
  89. package/dist/tab-bar.js +1 -1
  90. package/dist/tab-scaffold.d.ts +6 -6
  91. package/dist/tab-scaffold.js.map +1 -1
  92. package/dist/token-search-field.d.ts +25 -9
  93. package/dist/token-search-field.js +1 -1
  94. package/dist/toolbar-control-group.d.ts +9 -5
  95. package/dist/toolbar-control-group.js +1 -1
  96. package/dist/toolbar-text.d.ts +10 -11
  97. package/dist/toolbar-text.js +1 -1
  98. package/dist/wire-tab-bars.d.ts +5 -2
  99. package/dist/wire-tab-bars.js +1 -1
  100. package/dist/wire-token-search-fields.d.ts +11 -6
  101. package/dist/wire-token-search-fields.js +1 -1
  102. package/docs/catalog.md +186 -67
  103. package/docs/component-contract.md +102 -5
  104. package/docs/component-selection.md +9 -5
  105. package/docs/design-philosophy.md +2 -4
  106. package/docs/examples/application-ui-profile.json +60 -0
  107. package/docs/examples/component-catalog-extension-v2.json +81 -0
  108. package/docs/layout.md +42 -23
  109. package/docs/sunken-panel.md +43 -0
  110. package/docs/type-contracts.md +99 -0
  111. package/docs/ui-analyzer.md +81 -0
  112. package/docs/ui-doctor.md +77 -0
  113. package/docs/ui-evaluator.md +122 -0
  114. package/doctor/cli.mjs +115 -0
  115. package/doctor/config.schema.json +68 -0
  116. package/doctor/index.d.mts +116 -0
  117. package/doctor/index.mjs +1256 -0
  118. package/doctor/report.schema.json +118 -0
  119. package/evaluator/cli.mjs +89 -0
  120. package/evaluator/index.d.mts +90 -0
  121. package/evaluator/index.mjs +1138 -0
  122. package/evaluator/report.schema.json +152 -0
  123. package/llms.txt +40 -3
  124. package/package.json +66 -8
  125. package/ux-demo/recipes/compact-toolbar.tsx +2 -2
  126. package/ux-demo/recipes/composer-form.tsx +1 -1
  127. package/dist/chunk-4TRDV6RL.js.map +0 -1
  128. package/dist/chunk-6ETZKKAE.js.map +0 -1
  129. package/dist/chunk-ENVTNLYC.js.map +0 -1
  130. package/dist/chunk-JTHUXDN2.js.map +0 -1
  131. package/dist/chunk-NVWXKU37.js.map +0 -1
  132. package/dist/chunk-QIRXYKCE.js.map +0 -1
  133. package/dist/chunk-S3HKPI7I.js.map +0 -1
  134. package/dist/chunk-S3PCZ7NV.js.map +0 -1
  135. package/dist/chunk-U5IFGQCD.js.map +0 -1
  136. package/dist/chunk-ZK2G3VR5.js.map +0 -1
  137. package/dist/chunk-ZVI5YST2.js.map +0 -1
package/doctor/cli.mjs ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/env node
2
+
3
+ import console from 'node:console';
4
+ import { execFile } from 'node:child_process';
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { resolve } from 'node:path';
7
+ import process from 'node:process';
8
+ import { promisify } from 'node:util';
9
+
10
+ import { formatUiDoctorText, runUiDoctor, UI_DOCTOR_EXIT } from './index.mjs';
11
+
12
+ const execFileAsync = promisify(execFile);
13
+ const args = process.argv.slice(2);
14
+ const take = (name, repeat = false) => {
15
+ const values = [];
16
+ while (args.includes(name)) {
17
+ const index = args.indexOf(name);
18
+ const value = args[index + 1];
19
+ if (!value || value.startsWith('--'))
20
+ throw new Error(`${name} requires a value.`);
21
+ args.splice(index, 2);
22
+ values.push(value);
23
+ if (!repeat) break;
24
+ }
25
+ return repeat ? values : values[0];
26
+ };
27
+
28
+ async function changedPaths(root) {
29
+ const { stdout: tracked } = await execFileAsync(
30
+ 'git',
31
+ ['diff', '--name-only', '--diff-filter=ACMR', 'HEAD', '--'],
32
+ { cwd: root },
33
+ );
34
+ const { stdout: untracked } = await execFileAsync(
35
+ 'git',
36
+ ['ls-files', '--others', '--exclude-standard'],
37
+ { cwd: root },
38
+ );
39
+ return [
40
+ ...new Set(`${tracked}\n${untracked}`.split(/\r?\n/).filter(Boolean)),
41
+ ].sort();
42
+ }
43
+
44
+ try {
45
+ if (args.includes('--help') || args.includes('-h')) {
46
+ console.log(`Usage: kerf-ui-doctor [--full | --changed] [options]
47
+
48
+ Options:
49
+ --root <path> Workspace root (default: current directory)
50
+ --package <name|path> Select one workspace package
51
+ --path <path> Restrict changed mode (repeatable)
52
+ --config <path> Doctor config (default: .kerf-ui-doctor.json)
53
+ --browser-url <url> Explicitly enable browser evaluation
54
+ --browser <list> Comma-separated chromium,firefox,webkit
55
+ --eslint <preset> recommended-ui or strict-ui
56
+ --format <format> text or json (default: text)
57
+ --output <path> Also write the merged report
58
+ --no-cache Disable the content-addressed cache
59
+
60
+ Exit codes: 0 clean, 1 findings, 2 configuration/tool failure, 130 cancelled.`);
61
+ process.exit(0);
62
+ }
63
+ const root = resolve(take('--root') ?? process.cwd());
64
+ const packageSelector = take('--package');
65
+ const explicitPaths = take('--path', true);
66
+ const configPath = take('--config');
67
+ const url = take('--browser-url');
68
+ const browserList = take('--browser');
69
+ const eslintConfig = take('--eslint') ?? 'recommended-ui';
70
+ const format = take('--format') ?? 'text';
71
+ const output = take('--output');
72
+ const full = args.includes('--full');
73
+ const changed = args.includes('--changed');
74
+ const noCache = args.includes('--no-cache');
75
+ const known = new Set(['--full', '--changed', '--no-cache']);
76
+ const unknown = args.filter((item) => !known.has(item));
77
+ if (unknown.length)
78
+ throw new Error(`Unknown arguments: ${unknown.join(' ')}`);
79
+ if (full && changed)
80
+ throw new Error('--full and --changed are mutually exclusive.');
81
+ if (!['text', 'json'].includes(format))
82
+ throw new Error('--format must be text or json.');
83
+ if (!['recommended-ui', 'strict-ui'].includes(eslintConfig))
84
+ throw new Error('--eslint must be recommended-ui or strict-ui.');
85
+ const abort = new globalThis.AbortController();
86
+ process.once('SIGINT', () => abort.abort());
87
+ const paths =
88
+ changed && !explicitPaths.length ? await changedPaths(root) : explicitPaths;
89
+ const report = await runUiDoctor({
90
+ root,
91
+ package: packageSelector,
92
+ mode: changed ? 'changed' : full ? 'full' : undefined,
93
+ paths,
94
+ configPath,
95
+ cache: !noCache,
96
+ signal: abort.signal,
97
+ eslintConfig,
98
+ browser: url
99
+ ? { url, browsers: browserList?.split(',').map((item) => item.trim()) }
100
+ : undefined,
101
+ });
102
+ const rendered =
103
+ format === 'json'
104
+ ? JSON.stringify(report, null, 2)
105
+ : formatUiDoctorText(report);
106
+ if (output) await writeFile(resolve(root, output), `${rendered}\n`);
107
+ else console.log(rendered);
108
+ process.exitCode = report.exitCode;
109
+ } catch (error) {
110
+ console.error(`kerf-ui-doctor: ${error.message}`);
111
+ process.exitCode =
112
+ process.exitCode === UI_DOCTOR_EXIT.cancelled
113
+ ? process.exitCode
114
+ : UI_DOCTOR_EXIT.configuration;
115
+ }
@@ -0,0 +1,68 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/doctor/config.schema.json",
4
+ "title": "Kerf UI doctor configuration",
5
+ "type": "object",
6
+ "required": ["schemaVersion"],
7
+ "properties": {
8
+ "$schema": { "type": "string" },
9
+ "schemaVersion": { "const": 1 },
10
+ "mode": { "enum": ["full", "changed"] },
11
+ "package": { "type": "string", "minLength": 1 },
12
+ "stages": {
13
+ "type": "object",
14
+ "properties": {
15
+ "catalog": { "type": "boolean" },
16
+ "typescript": { "type": "boolean" },
17
+ "eslint": { "type": "boolean" },
18
+ "analyzer": { "type": "boolean" },
19
+ "browser": { "type": "boolean" }
20
+ },
21
+ "additionalProperties": false
22
+ },
23
+ "browser": {
24
+ "type": "object",
25
+ "required": ["url"],
26
+ "properties": {
27
+ "url": { "type": "string", "pattern": "^https?://" },
28
+ "browsers": {
29
+ "type": "array",
30
+ "items": { "enum": ["chromium", "firefox", "webkit"] },
31
+ "uniqueItems": true
32
+ },
33
+ "retention": { "enum": ["always", "on-failure", "never"] },
34
+ "outputDirectory": { "$ref": "#/$defs/path" },
35
+ "reportPath": { "$ref": "#/$defs/path" }
36
+ },
37
+ "additionalProperties": false
38
+ },
39
+ "cache": { "type": "boolean" },
40
+ "suppressions": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "required": ["id", "rules", "target", "rationale"],
45
+ "properties": {
46
+ "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
47
+ "rules": {
48
+ "type": "array",
49
+ "items": { "type": "string", "minLength": 1 },
50
+ "minItems": 1,
51
+ "uniqueItems": true
52
+ },
53
+ "target": { "$ref": "#/$defs/path" },
54
+ "rationale": { "type": "string", "minLength": 12 }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ }
59
+ },
60
+ "additionalProperties": false,
61
+ "$defs": {
62
+ "path": {
63
+ "type": "string",
64
+ "minLength": 1,
65
+ "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$))(?!.*[?*]).+"
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,116 @@
1
+ export type UiDoctorSeverity = 'error' | 'warning' | 'review' | 'info';
2
+ export type UiDoctorStageId =
3
+ | 'catalog'
4
+ | 'typescript'
5
+ | 'eslint'
6
+ | 'analyzer'
7
+ | 'browser'
8
+ | 'doctor'
9
+ | 'merged';
10
+ export type UiDoctorStageStatus =
11
+ 'ran' | 'cached' | 'skipped' | 'unavailable' | 'failed' | 'cancelled';
12
+
13
+ export interface UiDoctorSuppression {
14
+ id: string;
15
+ rules: string[];
16
+ target: string;
17
+ rationale: string;
18
+ }
19
+
20
+ export interface UiDoctorConfig {
21
+ $schema?: string;
22
+ schemaVersion: 1;
23
+ mode?: 'full' | 'changed';
24
+ package?: string;
25
+ stages?: Partial<
26
+ Record<
27
+ 'catalog' | 'typescript' | 'eslint' | 'analyzer' | 'browser',
28
+ boolean
29
+ >
30
+ >;
31
+ browser?: {
32
+ url: string;
33
+ browsers?: Array<'chromium' | 'firefox' | 'webkit'>;
34
+ retention?: 'always' | 'on-failure' | 'never';
35
+ outputDirectory?: string;
36
+ reportPath?: string;
37
+ };
38
+ cache?: boolean;
39
+ suppressions?: UiDoctorSuppression[];
40
+ }
41
+
42
+ export interface UiDoctorDiagnostic {
43
+ id: string;
44
+ severity: UiDoctorSeverity;
45
+ stage: UiDoctorStageId | string;
46
+ message: string;
47
+ location?: { file: string; line: number; column: number; path?: string };
48
+ dom?: { context?: string; selector: string };
49
+ evidence?: unknown;
50
+ catalogFacts?: unknown;
51
+ documentation?: string;
52
+ action?: string;
53
+ sources?: string[];
54
+ }
55
+
56
+ export interface UiDoctorReport {
57
+ schemaVersion: 1;
58
+ tool: { name: '@kerfjs/ui/doctor'; reportVersion: 1 };
59
+ mode: 'full' | 'changed';
60
+ root: '.';
61
+ package: string;
62
+ paths: string[];
63
+ stages: Array<{ id: string; status: UiDoctorStageStatus; detail?: string }>;
64
+ diagnostics: UiDoctorDiagnostic[];
65
+ suppressions: Array<
66
+ UiDoctorDiagnostic & { suppression: { id: string; rationale: string } }
67
+ >;
68
+ summary: {
69
+ errors: number;
70
+ warnings: number;
71
+ review: number;
72
+ suppressed: number;
73
+ };
74
+ exitCode: 0 | 1 | 2 | 130;
75
+ cache: { hit: boolean; key: string };
76
+ }
77
+
78
+ export const UI_DOCTOR_SCHEMA_VERSION: 1;
79
+ export const UI_DOCTOR_EXIT: Readonly<{
80
+ clean: 0;
81
+ findings: 1;
82
+ configuration: 2;
83
+ cancelled: 130;
84
+ }>;
85
+ export const UI_DOCTOR_RULES: Readonly<
86
+ Record<string, { severity: UiDoctorSeverity; title: string }>
87
+ >;
88
+ export function validateUiDoctorConfig(
89
+ config: unknown,
90
+ source?: string,
91
+ ): UiDoctorDiagnostic[];
92
+ export function readUiDoctorConfig(
93
+ root: string,
94
+ path?: string,
95
+ ): Promise<{
96
+ path: string;
97
+ config: UiDoctorConfig;
98
+ diagnostics: UiDoctorDiagnostic[];
99
+ }>;
100
+ export function resolveUiDoctorPackage(
101
+ root: string,
102
+ selector?: string,
103
+ ): Promise<string>;
104
+ export function runUiDoctor(options?: {
105
+ root?: string;
106
+ package?: string;
107
+ mode?: 'full' | 'changed';
108
+ paths?: string[];
109
+ config?: UiDoctorConfig;
110
+ configPath?: string;
111
+ cache?: boolean;
112
+ signal?: AbortSignal;
113
+ browser?: UiDoctorConfig['browser'];
114
+ eslintConfig?: 'recommended-ui' | 'strict-ui';
115
+ }): Promise<UiDoctorReport>;
116
+ export function formatUiDoctorText(report: UiDoctorReport): string;