@estiva-app/ui 0.26.0 → 0.28.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.
Files changed (62) hide show
  1. package/dist/gates/app-checks.d.ts.map +1 -1
  2. package/dist/gates/{chunk-GTQZEHPC.js → chunk-I7CJLUPM.js} +13 -1
  3. package/dist/gates/chunk-I7CJLUPM.js.map +7 -0
  4. package/dist/gates/{chunk-EA33NP5B.js → chunk-XE6HC5MJ.js} +8 -12
  5. package/dist/gates/chunk-XE6HC5MJ.js.map +7 -0
  6. package/dist/gates/cli.js +1 -1
  7. package/dist/gates/create-app.d.ts.map +1 -1
  8. package/dist/gates/create-app.js +1 -1
  9. package/dist/gates/gate-config.d.ts +1 -1
  10. package/dist/gates/gate-config.d.ts.map +1 -1
  11. package/dist/gates/index.d.ts +2 -1
  12. package/dist/gates/index.d.ts.map +1 -1
  13. package/dist/gates/index.js +36 -6
  14. package/dist/gates/index.js.map +2 -2
  15. package/dist/gates/status.d.ts +2 -0
  16. package/dist/gates/status.d.ts.map +1 -1
  17. package/dist/gates/token-lint.d.ts +27 -1
  18. package/dist/gates/token-lint.d.ts.map +1 -1
  19. package/dist/registry/{app-DQI6WLHR.js → app-FXN4EI34.js} +2 -2
  20. package/dist/registry/app.d.ts.map +1 -1
  21. package/dist/registry/{chunk-W2B2G7OE.js → chunk-6OH3TWGF.js} +59 -1
  22. package/dist/registry/chunk-6OH3TWGF.js.map +7 -0
  23. package/dist/registry/{chunk-WMFF3MPP.js → chunk-XCBN2EKW.js} +3 -2
  24. package/dist/registry/{chunk-WMFF3MPP.js.map → chunk-XCBN2EKW.js.map} +2 -2
  25. package/dist/registry/cli.js +69 -8
  26. package/dist/registry/cli.js.map +3 -3
  27. package/dist/registry/contract.d.ts +29 -0
  28. package/dist/registry/contract.d.ts.map +1 -0
  29. package/dist/registry/gated-5CPIUQAW.js +230 -0
  30. package/dist/registry/gated-5CPIUQAW.js.map +7 -0
  31. package/dist/registry/gated.d.ts +31 -0
  32. package/dist/registry/gated.d.ts.map +1 -0
  33. package/dist/registry/index.d.ts +1 -0
  34. package/dist/registry/index.d.ts.map +1 -1
  35. package/dist/registry/index.js +15 -3
  36. package/package.json +1 -1
  37. package/registry.json +1 -1
  38. package/src/CollapsibleSection.stories.tsx +6 -0
  39. package/src/ListColumn.stories.tsx +6 -0
  40. package/src/MenuItem.stories.tsx +151 -135
  41. package/src/Person.stories.tsx +7 -0
  42. package/src/PersonTrigger.stories.tsx +7 -0
  43. package/src/SectionHeader.stories.tsx +5 -0
  44. package/src/gates/app-checks.ts +19 -2
  45. package/src/gates/create-app.ts +7 -11
  46. package/src/gates/gate-config.ts +3 -2
  47. package/src/gates/gates.test.ts +35 -1
  48. package/src/gates/index.ts +2 -1
  49. package/src/gates/status.ts +15 -0
  50. package/src/gates/token-lint.ts +47 -2
  51. package/src/registry/app.test.ts +14 -1
  52. package/src/registry/app.ts +5 -1
  53. package/src/registry/cli.ts +79 -7
  54. package/src/registry/contract.test.ts +158 -0
  55. package/src/registry/contract.ts +108 -0
  56. package/src/registry/gated.test.ts +98 -0
  57. package/src/registry/gated.ts +308 -0
  58. package/src/registry/index.ts +1 -0
  59. package/dist/gates/chunk-EA33NP5B.js.map +0 -7
  60. package/dist/gates/chunk-GTQZEHPC.js.map +0 -7
  61. package/dist/registry/chunk-W2B2G7OE.js.map +0 -7
  62. /package/dist/registry/{app-DQI6WLHR.js.map → app-FXN4EI34.js.map} +0 -0
@@ -0,0 +1,29 @@
1
+ import type { Registry } from './schema';
2
+ /** The four headings, in the order a page carries them. */
3
+ export declare const PAGE_SECTIONS: readonly ["When", "When not", "How", "What it owns"];
4
+ /** The classes that owe a page and a picture. */
5
+ export declare const CONTRACT_KINDS: readonly ["reusable", "promote-candidate"];
6
+ /**
7
+ * Why a page breaks the contract, or null when it keeps it.
8
+ *
9
+ * Read on normalised text: the apps are checked out with CRLF on Windows.
10
+ */
11
+ export declare function pageProblem(source: string): string | null;
12
+ /** Whether a page records where the part is drawn, in place of a story of its own. */
13
+ export declare function hasSeenIn(source: string): boolean;
14
+ /**
15
+ * Every catalogue link that names a story or a docs page its Storybook does not
16
+ * have (UIG-19: a dead link is a build failure, not a dead end found six weeks
17
+ * later). `ids` is every entry id in the Storybook's own index — what
18
+ * `storybook index` writes — so this is Storybook's answer, not a guess at how
19
+ * it builds an id.
20
+ */
21
+ export declare function linkProblems(registry: Registry, ids: ReadonlySet<string>): string[];
22
+ /**
23
+ * Every way an app's catalogue breaks the contract, one line each, naming the
24
+ * part, its file and what to do. Empty when it keeps it.
25
+ *
26
+ * `root` is the app's own folder: the one its catalogue's paths are relative to.
27
+ */
28
+ export declare function contractProblems(registry: Registry, root: string): string[];
29
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/registry/contract.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,2DAA2D;AAC3D,eAAO,MAAM,aAAa,sDAAuD,CAAA;AAEjF,iDAAiD;AACjD,eAAO,MAAM,cAAc,4CAA6C,CAAA;AAKxE;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBzD;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CASnF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqB3E"}
@@ -0,0 +1,230 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/registry/gated.ts
4
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5
+ import { dirname, join } from "node:path";
6
+ import ts from "typescript";
7
+ var parse = (file, text) => ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
8
+ function declared(sf, name) {
9
+ let found = null;
10
+ const unwrap = (e) => {
11
+ if (!e) return null;
12
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return e;
13
+ if (ts.isCallExpression(e)) return unwrap(e.arguments[0]);
14
+ if (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e)) return unwrap(e.expression);
15
+ return null;
16
+ };
17
+ for (const s of sf.statements) {
18
+ if (ts.isFunctionDeclaration(s) && s.name?.text === name) found = s;
19
+ if (ts.isVariableStatement(s)) {
20
+ for (const d of s.declarationList.declarations) if (ts.isIdentifier(d.name) && d.name.text === name) found = unwrap(d.initializer);
21
+ }
22
+ }
23
+ return found;
24
+ }
25
+ function propsOf(fn) {
26
+ const locals = /* @__PURE__ */ new Map();
27
+ const first = fn.parameters[0];
28
+ if (!first) return { locals, object: null };
29
+ if (ts.isObjectBindingPattern(first.name)) {
30
+ for (const el of first.name.elements) {
31
+ if (el.dotDotDotToken || !ts.isIdentifier(el.name)) continue;
32
+ const prop = el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text;
33
+ locals.set(el.name.text, prop);
34
+ }
35
+ return { locals, object: null };
36
+ }
37
+ return { locals, object: ts.isIdentifier(first.name) ? first.name.text : null };
38
+ }
39
+ var drawsJsx = (node) => {
40
+ let yes = false;
41
+ const walk = (n) => {
42
+ if (yes) return;
43
+ if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) yes = true;
44
+ else ts.forEachChild(n, walk);
45
+ };
46
+ walk(node);
47
+ return yes;
48
+ };
49
+ function gates(fn) {
50
+ const { locals, object } = propsOf(fn);
51
+ const out = /* @__PURE__ */ new Set();
52
+ const propIn = (e) => {
53
+ while (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e)) e = e.expression;
54
+ if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken && ts.isPrefixUnaryExpression(e.operand)) return propIn(e.operand.operand);
55
+ if (ts.isBinaryExpression(e) && [ts.SyntaxKind.ExclamationEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(e.operatorToken.kind)) return propIn(e.left);
56
+ if (ts.isIdentifier(e)) return locals.get(e.text) ?? null;
57
+ if (object && ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === object) return e.name.text;
58
+ return null;
59
+ };
60
+ const walk = (n) => {
61
+ if (n !== fn && ts.isFunctionDeclaration(n)) return;
62
+ if (ts.isJsxExpression(n) && n.expression) {
63
+ const e = n.expression;
64
+ if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && drawsJsx(e.right)) {
65
+ const p = propIn(e.left);
66
+ if (p) out.add(p);
67
+ }
68
+ if (ts.isConditionalExpression(e) && drawsJsx(e.whenTrue) && !drawsJsx(e.whenFalse)) {
69
+ const p = propIn(e.condition);
70
+ if (p) out.add(p);
71
+ }
72
+ }
73
+ ts.forEachChild(n, walk);
74
+ };
75
+ if (fn.body) walk(fn.body);
76
+ return out;
77
+ }
78
+ var ANY = "*";
79
+ function passed(sf, tags) {
80
+ const out = /* @__PURE__ */ new Set();
81
+ const consts = /* @__PURE__ */ new Map();
82
+ for (const s of sf.statements) {
83
+ if (!ts.isVariableStatement(s)) continue;
84
+ for (const d of s.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer && ts.isObjectLiteralExpression(d.initializer)) consts.set(d.name.text, d.initializer);
85
+ }
86
+ const keysOf = (o, seen) => {
87
+ for (const p of o.properties) {
88
+ if (ts.isSpreadAssignment(p)) {
89
+ const e = p.expression;
90
+ const name = ts.isIdentifier(e) ? e.text : ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.name.text === "args" ? `${e.expression.text}.args` : null;
91
+ let target2;
92
+ if (name && !seen.has(name)) {
93
+ if (!name.endsWith(".args")) target2 = consts.get(name);
94
+ else {
95
+ const story = consts.get(name.slice(0, -".args".length));
96
+ const args = story?.properties.find((q) => ts.isPropertyAssignment(q) && ts.isIdentifier(q.name) && q.name.text === "args");
97
+ if (args && ts.isPropertyAssignment(args) && ts.isObjectLiteralExpression(args.initializer)) target2 = args.initializer;
98
+ }
99
+ }
100
+ if (target2) keysOf(target2, /* @__PURE__ */ new Set([...seen, name]));
101
+ else out.add(ANY);
102
+ } else if (p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) out.add(p.name.text);
103
+ }
104
+ };
105
+ const walk = (n) => {
106
+ if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === "args") {
107
+ if (ts.isObjectLiteralExpression(n.initializer)) keysOf(n.initializer, /* @__PURE__ */ new Set());
108
+ else out.add(ANY);
109
+ }
110
+ if ((ts.isJsxSelfClosingElement(n) || ts.isJsxOpeningElement(n)) && tags.includes(n.tagName.getText(sf))) {
111
+ for (const a of n.attributes.properties) {
112
+ if (ts.isJsxAttribute(a)) out.add(a.name.getText(sf));
113
+ else if (ts.isJsxSpreadAttribute(a)) {
114
+ const target2 = ts.isIdentifier(a.expression) ? consts.get(a.expression.text) : void 0;
115
+ if (target2) keysOf(target2, /* @__PURE__ */ new Set());
116
+ else if (!(ts.isIdentifier(a.expression) && a.expression.text === "args")) out.add(ANY);
117
+ }
118
+ }
119
+ }
120
+ ts.forEachChild(n, walk);
121
+ };
122
+ walk(sf);
123
+ return out;
124
+ }
125
+ function storyFiles(root) {
126
+ const out = [];
127
+ const walk = (dir) => {
128
+ for (const name of readdirSync(dir)) {
129
+ if (name === "node_modules" || name.startsWith(".")) continue;
130
+ const path = join(dir, name);
131
+ if (statSync(path).isDirectory()) walk(path);
132
+ else if (/\.stories\.tsx?$/.test(name)) out.push(path);
133
+ }
134
+ };
135
+ if (existsSync(join(root, "src"))) walk(join(root, "src"));
136
+ return out;
137
+ }
138
+ function handsOn(fn, sf) {
139
+ const { locals, object } = propsOf(fn);
140
+ const out = [];
141
+ const walk = (n) => {
142
+ if (n !== fn && ts.isFunctionDeclaration(n)) return;
143
+ if (ts.isJsxSelfClosingElement(n) || ts.isJsxOpeningElement(n)) {
144
+ const tag = n.tagName.getText(sf);
145
+ if (/^[A-Z]/.test(tag)) {
146
+ for (const a of n.attributes.properties) {
147
+ if (!ts.isJsxAttribute(a) || !a.initializer || !ts.isJsxExpression(a.initializer) || !a.initializer.expression) continue;
148
+ const e = a.initializer.expression;
149
+ const prop = ts.isIdentifier(e) ? locals.get(e.text) : object && ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === object ? e.name.text : void 0;
150
+ if (prop) out.push({ tag, attr: a.name.getText(sf), prop });
151
+ }
152
+ }
153
+ }
154
+ ts.forEachChild(n, walk);
155
+ };
156
+ if (fn.body) walk(fn.body);
157
+ return out;
158
+ }
159
+ function target(from, spec, root) {
160
+ if (spec.startsWith(".")) return join(dirname(from), spec).replace(/\.[jt]sx?$/, "");
161
+ if (spec.startsWith("@/")) return join(root, "src", spec.slice(2)).replace(/\.[jt]sx?$/, "");
162
+ return null;
163
+ }
164
+ function gatedFindings(registry, root, { kinds = ["reusable", "promote-candidate"], pictured = "own" } = {}) {
165
+ const findings = [];
166
+ const stories = storyFiles(root).map((file) => ({ file, sf: parse(file, readFileSync(file, "utf8")) }));
167
+ const sources = /* @__PURE__ */ new Map();
168
+ const sourceOf = (rel) => {
169
+ const path = join(root, rel);
170
+ if (!sources.has(path)) {
171
+ if (!existsSync(path)) return null;
172
+ sources.set(path, parse(path, readFileSync(path, "utf8")));
173
+ }
174
+ return sources.get(path);
175
+ };
176
+ const fileOf = new Map(registry.entries.filter((e) => e.kind === "component").map((e) => [e.name, e.sourceFile]));
177
+ const givenTo = /* @__PURE__ */ new Map();
178
+ const storiesOf = (name, rel) => {
179
+ const key = `${rel}#${name}`;
180
+ if (!givenTo.has(key)) {
181
+ const own = join(root, rel).replace(/\.[jt]sx?$/, "");
182
+ const drawing = stories.filter(
183
+ ({ file, sf: s }) => s.statements.some((st) => ts.isImportDeclaration(st) && ts.isStringLiteral(st.moduleSpecifier) && target(file, st.moduleSpecifier.text, root) === own)
184
+ );
185
+ givenTo.set(key, { drawn: drawing.length > 0, given: new Set(drawing.flatMap(({ sf: s }) => [...passed(s, [name, `${name}View`])])) });
186
+ }
187
+ return givenTo.get(key);
188
+ };
189
+ const memo = /* @__PURE__ */ new Map();
190
+ const gatesOf = (name, sf, depth) => {
191
+ const key = `${sf.fileName}#${name}`;
192
+ if (memo.has(key)) return memo.get(key);
193
+ memo.set(key, /* @__PURE__ */ new Set());
194
+ const fn = declared(sf, name);
195
+ const out = fn ? gates(fn) : /* @__PURE__ */ new Set();
196
+ if (fn && depth < 3) {
197
+ for (const { tag, attr, prop } of handsOn(fn, sf)) {
198
+ if (out.has(prop) || /Dialog$/.test(tag)) continue;
199
+ const here = declared(sf, tag) ? sf : fileOf.has(tag) ? sourceOf(fileOf.get(tag)) : null;
200
+ if (!here || !gatesOf(tag, here, depth + 1).has(attr)) continue;
201
+ const theirs = fileOf.get(tag);
202
+ if (pictured === "anywhere" && theirs && here !== sf) {
203
+ const { given } = storiesOf(tag, theirs);
204
+ if (given.has(attr) || given.has(ANY)) continue;
205
+ }
206
+ out.add(prop);
207
+ }
208
+ }
209
+ memo.set(key, out);
210
+ return out;
211
+ };
212
+ for (const entry of registry.entries) {
213
+ const cls = entry.app?.class ?? "component";
214
+ if (!kinds.includes(cls) || entry.kind !== "component") continue;
215
+ const sf = sourceOf(entry.sourceFile);
216
+ if (!sf) continue;
217
+ const { drawn, given } = storiesOf(entry.name, entry.sourceFile);
218
+ if (!drawn || given.has(ANY)) continue;
219
+ for (const name of [entry.name, `${entry.name}View`]) {
220
+ for (const prop of gatesOf(name, sf, 0)) {
221
+ if (!given.has(prop)) findings.push({ part: entry.name, file: entry.sourceFile.split("\\").join("/"), prop, in: name });
222
+ }
223
+ }
224
+ }
225
+ return findings;
226
+ }
227
+ export {
228
+ gatedFindings
229
+ };
230
+ //# sourceMappingURL=gated-5CPIUQAW.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/registry/gated.ts"],
4
+ "sourcesContent": ["/**\n * What a part draws only when a prop is passed, and no story passes it (UIG-19,\n * Katerina's ruling 5).\n *\n * `{onReply && <IconButton \u2026 />}` works in the app, because the app passes\n * `onReply`. In Storybook nothing does, so the button is simply not there. It\n * reads as a broken behaviour rather than a missing fixture, and it hides the\n * very state a reviewer came to see. UIG-17 found five in Peek by hand: Reply on\n * ConversationCard, Edit and Delete on ReplyCard, Rename and Copy link on\n * TopicMoreMenu, Open original on ThreadPanel.\n *\n * Read with TypeScript, part by part. The gate is looked for only inside the\n * part's own function (and its `\u2026View` half, when the stories draw that),\n * because a file is not a component: UIG-17's first scan blamed ReplyCard for\n * two props that belong to a menu declared beside it. A story passes a prop by\n * naming it in an `args` object or as an attribute on the part's tag, anywhere\n * in a story file that imports the part's file.\n */\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'\nimport { basename, dirname, join, relative } from 'node:path'\nimport ts from 'typescript'\nimport type { Registry } from './schema'\n\nexport interface GatedFinding {\n part: string\n file: string\n prop: string\n /** The part or view whose body holds the gate. */\n in: string\n}\n\nconst parse = (file: string, text: string) => ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)\n\n/** The function a name is declared as: `function X`, `const X = (\u2026) =>`, `memo(\u2026)`, `forwardRef(\u2026)`. */\nfunction declared(sf: ts.SourceFile, name: string): ts.FunctionLikeDeclaration | null {\n let found: ts.FunctionLikeDeclaration | null = null\n const unwrap = (e: ts.Expression | undefined): ts.FunctionLikeDeclaration | null => {\n if (!e) return null\n if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return e\n if (ts.isCallExpression(e)) return unwrap(e.arguments[0])\n if (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e)) return unwrap(e.expression)\n return null\n }\n for (const s of sf.statements) {\n if (ts.isFunctionDeclaration(s) && s.name?.text === name) found = s\n if (ts.isVariableStatement(s)) for (const d of s.declarationList.declarations) if (ts.isIdentifier(d.name) && d.name.text === name) found = unwrap(d.initializer)\n }\n return found\n}\n\n/** The props a function takes, by the name its body uses: `{ onReply, owner: who }` \u2192 onReply\u2192onReply, who\u2192owner. */\nfunction propsOf(fn: ts.FunctionLikeDeclaration): { locals: Map<string, string>; object: string | null } {\n const locals = new Map<string, string>()\n const first = fn.parameters[0]\n if (!first) return { locals, object: null }\n if (ts.isObjectBindingPattern(first.name)) {\n for (const el of first.name.elements) {\n if (el.dotDotDotToken || !ts.isIdentifier(el.name)) continue\n const prop = el.propertyName && ts.isIdentifier(el.propertyName) ? el.propertyName.text : el.name.text\n locals.set(el.name.text, prop)\n }\n return { locals, object: null }\n }\n return { locals, object: ts.isIdentifier(first.name) ? first.name.text : null }\n}\n\nconst drawsJsx = (node: ts.Node): boolean => {\n let yes = false\n const walk = (n: ts.Node) => {\n if (yes) return\n if (ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n) || ts.isJsxFragment(n)) yes = true\n else ts.forEachChild(n, walk)\n }\n walk(node)\n return yes\n}\n\n/** Every prop that gates something drawn: `{p && <X/>}`, `{p ? <X/> : null}`, `{!!p && \u2026}`, `{p != null && \u2026}`. */\nfunction gates(fn: ts.FunctionLikeDeclaration): Set<string> {\n const { locals, object } = propsOf(fn)\n const out = new Set<string>()\n const propIn = (e: ts.Expression): string | null => {\n while (ts.isParenthesizedExpression(e) || ts.isNonNullExpression(e)) e = e.expression\n if (ts.isPrefixUnaryExpression(e) && e.operator === ts.SyntaxKind.ExclamationToken && ts.isPrefixUnaryExpression(e.operand)) return propIn(e.operand.operand)\n if (ts.isBinaryExpression(e) && [ts.SyntaxKind.ExclamationEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(e.operatorToken.kind)) return propIn(e.left)\n if (ts.isIdentifier(e)) return locals.get(e.text) ?? null\n if (object && ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === object) return e.name.text\n return null\n }\n const walk = (n: ts.Node) => {\n // A function declared inside is its own component or handler: not this part's markup.\n if (n !== fn && ts.isFunctionDeclaration(n)) return\n if (ts.isJsxExpression(n) && n.expression) {\n const e = n.expression\n if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && drawsJsx(e.right)) {\n const p = propIn(e.left)\n if (p) out.add(p)\n }\n if (ts.isConditionalExpression(e) && drawsJsx(e.whenTrue) && !drawsJsx(e.whenFalse)) {\n const p = propIn(e.condition)\n if (p) out.add(p)\n }\n }\n ts.forEachChild(n, walk)\n }\n if (fn.body) walk(fn.body)\n return out\n}\n\n/** Stands for \"every prop\": a spread the reader cannot open might pass any of them. */\nconst ANY = '*'\n\n/**\n * Every prop name a story file passes: keys of any `args` object, and\n * attributes on the named tags. A spread of an object declared in the same\n * file is opened (`args: { replyCount: 3, ...replyRow }`); one it cannot open\n * \u2014 an import, a call \u2014 counts as passing everything, so the check stays quiet\n * rather than wrong.\n */\nfunction passed(sf: ts.SourceFile, tags: string[]): Set<string> {\n const out = new Set<string>()\n const consts = new Map<string, ts.ObjectLiteralExpression>()\n for (const s of sf.statements) {\n if (!ts.isVariableStatement(s)) continue\n for (const d of s.declarationList.declarations) if (ts.isIdentifier(d.name) && d.initializer && ts.isObjectLiteralExpression(d.initializer)) consts.set(d.name.text, d.initializer)\n }\n const keysOf = (o: ts.ObjectLiteralExpression, seen: Set<string>) => {\n for (const p of o.properties) {\n if (ts.isSpreadAssignment(p)) {\n // `...replyRow`, or another story's args: `...Topic.args`.\n const e = p.expression\n const name = ts.isIdentifier(e) ? e.text : ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.name.text === 'args' ? `${e.expression.text}.args` : null\n let target: ts.ObjectLiteralExpression | undefined\n if (name && !seen.has(name)) {\n if (!name.endsWith('.args')) target = consts.get(name)\n else {\n const story = consts.get(name.slice(0, -'.args'.length))\n const args = story?.properties.find((q) => ts.isPropertyAssignment(q) && ts.isIdentifier(q.name) && q.name.text === 'args')\n if (args && ts.isPropertyAssignment(args) && ts.isObjectLiteralExpression(args.initializer)) target = args.initializer\n }\n }\n if (target) keysOf(target, new Set([...seen, name!]))\n else out.add(ANY)\n } else if (p.name && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) out.add(p.name.text)\n }\n }\n const walk = (n: ts.Node) => {\n if (ts.isPropertyAssignment(n) && ts.isIdentifier(n.name) && n.name.text === 'args') {\n if (ts.isObjectLiteralExpression(n.initializer)) keysOf(n.initializer, new Set())\n else out.add(ANY)\n }\n if ((ts.isJsxSelfClosingElement(n) || ts.isJsxOpeningElement(n)) && tags.includes(n.tagName.getText(sf))) {\n for (const a of n.attributes.properties) {\n if (ts.isJsxAttribute(a)) out.add(a.name.getText(sf))\n else if (ts.isJsxSpreadAttribute(a)) {\n const target = ts.isIdentifier(a.expression) ? consts.get(a.expression.text) : undefined\n if (target) keysOf(target, new Set())\n // `{...args}` hands on what `args` holds, and those are read where they are written.\n else if (!(ts.isIdentifier(a.expression) && a.expression.text === 'args')) out.add(ANY)\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n walk(sf)\n return out\n}\n\nfunction storyFiles(root: string): string[] {\n const out: string[] = []\n const walk = (dir: string) => {\n for (const name of readdirSync(dir)) {\n if (name === 'node_modules' || name.startsWith('.')) continue\n const path = join(dir, name)\n if (statSync(path).isDirectory()) walk(path)\n else if (/\\.stories\\.tsx?$/.test(name)) out.push(path)\n }\n }\n if (existsSync(join(root, 'src'))) walk(join(root, 'src'))\n return out\n}\n\n/** The props a part hands straight on to another tag: `<QuickActions onReply={onReply} />` \u2192 QuickActions, onReply, onReply. */\nfunction handsOn(fn: ts.FunctionLikeDeclaration, sf: ts.SourceFile): { tag: string; attr: string; prop: string }[] {\n const { locals, object } = propsOf(fn)\n const out: { tag: string; attr: string; prop: string }[] = []\n const walk = (n: ts.Node) => {\n if (n !== fn && ts.isFunctionDeclaration(n)) return\n if (ts.isJsxSelfClosingElement(n) || ts.isJsxOpeningElement(n)) {\n const tag = n.tagName.getText(sf)\n if (/^[A-Z]/.test(tag)) {\n for (const a of n.attributes.properties) {\n if (!ts.isJsxAttribute(a) || !a.initializer || !ts.isJsxExpression(a.initializer) || !a.initializer.expression) continue\n const e = a.initializer.expression\n const prop = ts.isIdentifier(e) ? locals.get(e.text) : object && ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression) && e.expression.text === object ? e.name.text : undefined\n if (prop) out.push({ tag, attr: a.name.getText(sf), prop })\n }\n }\n }\n ts.forEachChild(n, walk)\n }\n if (fn.body) walk(fn.body)\n return out\n}\n\n/** Where a module specifier points, without its extension: `./X`, or Vite's `@/` for `src/`. */\nfunction target(from: string, spec: string, root: string): string | null {\n if (spec.startsWith('.')) return join(dirname(from), spec).replace(/\\.[jt]sx?$/, '')\n if (spec.startsWith('@/')) return join(root, 'src', spec.slice(2)).replace(/\\.[jt]sx?$/, '')\n return null\n}\n\n/**\n * Every prop that gates something a part draws, where some story draws the\n * part and none of those stories passes the prop.\n *\n * \"Draws the part\" means a story file imports the part's own file \u2014 its own\n * stories, or an umbrella sheet such as Peek's Overlays/Menus, which is how\n * TopicMoreMenu lost Rename and Copy link. A gate a part only hands on counts\n * as its own: ConversationCard hands `onReply` to the strip that draws Reply,\n * so a ConversationCard story that never passes it shows no Reply. Followed\n * three parts deep at most.\n */\nexport interface GatedOptions {\n /** The classes checked. The contract's by default. */\n kinds?: readonly string[]\n /**\n * `own`: a part's own stories show everything it can draw \u2014 ConversationCard's\n * stories must show Reply. `anywhere`: a handed-on gate counts as shown when\n * the part it is handed to has a story that shows it. Katerina rules which.\n */\n pictured?: 'own' | 'anywhere'\n}\n\nexport function gatedFindings(registry: Registry, root: string, { kinds = ['reusable', 'promote-candidate'], pictured = 'own' }: GatedOptions = {}): GatedFinding[] {\n const findings: GatedFinding[] = []\n const stories = storyFiles(root).map((file) => ({ file, sf: parse(file, readFileSync(file, 'utf8')) }))\n const sources = new Map<string, ts.SourceFile>()\n const sourceOf = (rel: string): ts.SourceFile | null => {\n const path = join(root, rel)\n if (!sources.has(path)) {\n if (!existsSync(path)) return null\n sources.set(path, parse(path, readFileSync(path, 'utf8')))\n }\n return sources.get(path)!\n }\n const fileOf = new Map(registry.entries.filter((e) => e.kind === 'component').map((e) => [e.name, e.sourceFile]))\n\n /** The story files that import a file, and every prop they pass to the part or its view. */\n const givenTo = new Map<string, { drawn: boolean; given: Set<string> }>()\n const storiesOf = (name: string, rel: string) => {\n const key = `${rel}#${name}`\n if (!givenTo.has(key)) {\n const own = join(root, rel).replace(/\\.[jt]sx?$/, '')\n const drawing = stories.filter(({ file, sf: s }) =>\n s.statements.some((st) => ts.isImportDeclaration(st) && ts.isStringLiteral(st.moduleSpecifier) && target(file, st.moduleSpecifier.text, root) === own),\n )\n givenTo.set(key, { drawn: drawing.length > 0, given: new Set(drawing.flatMap(({ sf: s }) => [...passed(s, [name, `${name}View`])])) })\n }\n return givenTo.get(key)!\n }\n\n const memo = new Map<string, Set<string>>()\n /** The props a named part gates on, its own and those it hands to a part that gates. */\n const gatesOf = (name: string, sf: ts.SourceFile, depth: number): Set<string> => {\n const key = `${sf.fileName}#${name}`\n if (memo.has(key)) return memo.get(key)!\n memo.set(key, new Set())\n const fn = declared(sf, name)\n const out = fn ? gates(fn) : new Set<string>()\n if (fn && depth < 3) {\n for (const { tag, attr, prop } of handsOn(fn, sf)) {\n // A dialog is shut in a story until someone opens it, so nothing it draws\n // is missing from the picture (Katerina, 23 September).\n if (out.has(prop) || /Dialog$/.test(tag)) continue\n const here = declared(sf, tag) ? sf : fileOf.has(tag) ? sourceOf(fileOf.get(tag)!) : null\n if (!here || !gatesOf(tag, here, depth + 1).has(attr)) continue\n // Already pictured one level down: CollapsibleSection hands `trailing` to\n // SectionHeader, and SectionHeader's own story shows the slot.\n const theirs = fileOf.get(tag)\n if (pictured === 'anywhere' && theirs && here !== sf) {\n const { given } = storiesOf(tag, theirs)\n if (given.has(attr) || given.has(ANY)) continue\n }\n out.add(prop)\n }\n }\n memo.set(key, out)\n return out\n }\n\n for (const entry of registry.entries) {\n // The package's entries carry no app class: every one of its components is one.\n const cls = entry.app?.class ?? 'component'\n if (!kinds.includes(cls) || entry.kind !== 'component') continue\n const sf = sourceOf(entry.sourceFile)\n if (!sf) continue\n const { drawn, given } = storiesOf(entry.name, entry.sourceFile)\n // No story draws it at all: the contract's \"drawn nowhere\" says so, not this.\n if (!drawn || given.has(ANY)) continue\n for (const name of [entry.name, `${entry.name}View`]) {\n for (const prop of gatesOf(name, sf, 0)) {\n if (!given.has(prop)) findings.push({ part: entry.name, file: entry.sourceFile.split('\\\\').join('/'), prop, in: name })\n }\n }\n }\n return findings\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["target"]
7
+ }
@@ -0,0 +1,31 @@
1
+ import type { Registry } from './schema';
2
+ export interface GatedFinding {
3
+ part: string;
4
+ file: string;
5
+ prop: string;
6
+ /** The part or view whose body holds the gate. */
7
+ in: string;
8
+ }
9
+ /**
10
+ * Every prop that gates something a part draws, where some story draws the
11
+ * part and none of those stories passes the prop.
12
+ *
13
+ * "Draws the part" means a story file imports the part's own file — its own
14
+ * stories, or an umbrella sheet such as Peek's Overlays/Menus, which is how
15
+ * TopicMoreMenu lost Rename and Copy link. A gate a part only hands on counts
16
+ * as its own: ConversationCard hands `onReply` to the strip that draws Reply,
17
+ * so a ConversationCard story that never passes it shows no Reply. Followed
18
+ * three parts deep at most.
19
+ */
20
+ export interface GatedOptions {
21
+ /** The classes checked. The contract's by default. */
22
+ kinds?: readonly string[];
23
+ /**
24
+ * `own`: a part's own stories show everything it can draw — ConversationCard's
25
+ * stories must show Reply. `anywhere`: a handed-on gate counts as shown when
26
+ * the part it is handed to has a story that shows it. Katerina rules which.
27
+ */
28
+ pictured?: 'own' | 'anywhere';
29
+ }
30
+ export declare function gatedFindings(registry: Registry, root: string, { kinds, pictured }?: GatedOptions): GatedFinding[];
31
+ //# sourceMappingURL=gated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gated.d.ts","sourceRoot":"","sources":["../../src/registry/gated.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAA;CACX;AAuLD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,UAAU,CAAA;CAC9B;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAyC,EAAE,QAAgB,EAAE,GAAE,YAAiB,GAAG,YAAY,EAAE,CAyElK"}
@@ -12,6 +12,7 @@
12
12
  */
13
13
  export { buildRegistry, readIndexExports, serializeRegistry, type BuildOptions } from './build';
14
14
  export { buildAppRegistry, type AppBuildOptions } from './app';
15
+ export { CONTRACT_KINDS, PAGE_SECTIONS, contractProblems, hasSeenIn, linkProblems, pageProblem } from './contract';
15
16
  export { docsLink, findInRegistries, findInRegistry, formatFindings, type Finding } from './find';
16
17
  export { CLASSES, SCHEMA_VERSION, validateRegistry, type AppFacts, type EntryBehaviour, type EntryClass, type FileWithoutPart, type EntryKind, type EntryStatus, type EntryVariant, type PurposeSource, type Registry, type RegistryEntry, type RegistryExclusion, } from './schema';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAA;AACjG,OAAO,EACL,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/registry/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAClH,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAA;AACjG,OAAO,EACL,OAAO,EACP,cAAc,EACd,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,iBAAiB,GACvB,MAAM,UAAU,CAAA"}
@@ -1,13 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CONTRACT_KINDS,
4
+ PAGE_SECTIONS,
5
+ contractProblems,
3
6
  docsLink,
4
7
  findInRegistries,
5
8
  findInRegistry,
6
- formatFindings
7
- } from "./chunk-W2B2G7OE.js";
9
+ formatFindings,
10
+ hasSeenIn,
11
+ linkProblems,
12
+ pageProblem
13
+ } from "./chunk-6OH3TWGF.js";
8
14
  import {
9
15
  buildAppRegistry
10
- } from "./chunk-WMFF3MPP.js";
16
+ } from "./chunk-XCBN2EKW.js";
11
17
  import {
12
18
  buildRegistry,
13
19
  readIndexExports,
@@ -20,13 +26,19 @@ import {
20
26
  } from "./chunk-E4JNV7PC.js";
21
27
  export {
22
28
  CLASSES,
29
+ CONTRACT_KINDS,
30
+ PAGE_SECTIONS,
23
31
  SCHEMA_VERSION,
24
32
  buildAppRegistry,
25
33
  buildRegistry,
34
+ contractProblems,
26
35
  docsLink,
27
36
  findInRegistries,
28
37
  findInRegistry,
29
38
  formatFindings,
39
+ hasSeenIn,
40
+ linkProblems,
41
+ pageProblem,
30
42
  readIndexExports,
31
43
  serializeRegistry,
32
44
  validateRegistry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@estiva-app/ui",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "Estiva's design tokens (the contract) and a small set of primitives (a convenience) for every Estiva app.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/registry.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "kind": "package",
5
5
  "repo": "estiva-ui",
6
6
  "package": "@estiva-app/ui",
7
- "packageVersion": "0.26.0",
7
+ "packageVersion": "0.28.0",
8
8
  "exports": 87,
9
9
  "typeExports": 96,
10
10
  "files": null
@@ -1,5 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { IconPlus } from '@tabler/icons-react'
3
+ import { Chip } from './Chip'
3
4
  import { CollapsibleSection } from './CollapsibleSection'
4
5
  import { NavItem } from './NavItem'
5
6
 
@@ -33,6 +34,11 @@ export const WithActions: Story = {
33
34
  args: { actions: [{ icon: <IconPlus size={16} stroke={1.5} />, tooltip: 'Add', onClick: () => {} }] },
34
35
  }
35
36
 
37
+ /** A count beside the title, held on screen whether the section is open or closed. */
38
+ export const WithTrailing: Story = {
39
+ args: { trailing: <Chip type="brand" label="2" /> },
40
+ }
41
+
36
42
  /** Close it and reload the page: still closed. This browser remembers, under the key. */
37
43
  export const Remembered: Story = { args: { storageKey: 'estiva-ui.stories.collapsible-section' } }
38
44
 
@@ -59,6 +59,12 @@ export const Default: Story = {
59
59
  render: (args) => <ListColumn {...args}>{rows(6)}</ListColumn>,
60
60
  }
61
61
 
62
+ /** `chevron`: a chevron after the title, as ContainerHeader draws it, for a column whose title opens something. */
63
+ export const WithChevron: Story = {
64
+ args: { title: 'Items', chevron: true, actions },
65
+ render: (args) => <ListColumn {...args}>{rows(6)}</ListColumn>,
66
+ }
67
+
62
68
  /**
63
69
  * Groups. Each is headed by a package part, never by a row drawn by hand: a
64
70
  * group that folds is a `CollapsibleSection`; a group that does not fold is a