@gunkin/uiify 0.0.15-alpha → 0.0.16-alpha

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 (61) hide show
  1. package/README.md +34 -1
  2. package/agents/components.md +1 -0
  3. package/agents/index.md +7 -1
  4. package/agents/reference/components/table-of-contents.md +185 -0
  5. package/dist/components/index.d.ts +1 -0
  6. package/dist/components/index.d.ts.map +1 -1
  7. package/dist/components/index.js +1 -0
  8. package/dist/components/index.js.map +1 -1
  9. package/dist/components/table-of-contents/TableOfContents.d.ts +22 -0
  10. package/dist/components/table-of-contents/TableOfContents.d.ts.map +1 -0
  11. package/dist/components/table-of-contents/TableOfContents.js +142 -0
  12. package/dist/components/table-of-contents/TableOfContents.js.map +1 -0
  13. package/dist/components/table-of-contents/client/TableOfContentsClient.d.ts +18 -0
  14. package/dist/components/table-of-contents/client/TableOfContentsClient.d.ts.map +1 -0
  15. package/dist/components/table-of-contents/client/TableOfContentsClient.js +140 -0
  16. package/dist/components/table-of-contents/client/TableOfContentsClient.js.map +1 -0
  17. package/dist/components/table-of-contents/client/TableOfContentsClientParts.d.ts +7 -0
  18. package/dist/components/table-of-contents/client/TableOfContentsClientParts.d.ts.map +1 -0
  19. package/dist/components/table-of-contents/client/TableOfContentsClientParts.js +48 -0
  20. package/dist/components/table-of-contents/client/TableOfContentsClientParts.js.map +1 -0
  21. package/dist/components/table-of-contents/client/index.d.ts +5 -0
  22. package/dist/components/table-of-contents/client/index.d.ts.map +1 -0
  23. package/dist/components/table-of-contents/client/index.js +23 -0
  24. package/dist/components/table-of-contents/client/index.js.map +1 -0
  25. package/dist/components/table-of-contents/client/table-of-contents-context.d.ts +3 -0
  26. package/dist/components/table-of-contents/client/table-of-contents-context.d.ts.map +1 -0
  27. package/dist/components/table-of-contents/client/table-of-contents-context.js +7 -0
  28. package/dist/components/table-of-contents/client/table-of-contents-context.js.map +1 -0
  29. package/dist/components/table-of-contents/client/table-of-contents-interactions.d.ts +3 -0
  30. package/dist/components/table-of-contents/client/table-of-contents-interactions.d.ts.map +1 -0
  31. package/dist/components/table-of-contents/client/table-of-contents-interactions.js +144 -0
  32. package/dist/components/table-of-contents/client/table-of-contents-interactions.js.map +1 -0
  33. package/dist/components/table-of-contents/client/table-of-contents-pointer.d.ts +3 -0
  34. package/dist/components/table-of-contents/client/table-of-contents-pointer.d.ts.map +1 -0
  35. package/dist/components/table-of-contents/client/table-of-contents-pointer.js +94 -0
  36. package/dist/components/table-of-contents/client/table-of-contents-pointer.js.map +1 -0
  37. package/dist/components/table-of-contents/client.d.ts +1 -0
  38. package/dist/components/table-of-contents/client.js +1 -0
  39. package/dist/components/table-of-contents/index.d.ts +3 -0
  40. package/dist/components/table-of-contents/index.d.ts.map +1 -0
  41. package/dist/components/table-of-contents/index.js +21 -0
  42. package/dist/components/table-of-contents/index.js.map +1 -0
  43. package/dist/components/table-of-contents/scrollspy-selection.d.ts +4 -0
  44. package/dist/components/table-of-contents/scrollspy-selection.d.ts.map +1 -0
  45. package/dist/components/table-of-contents/scrollspy-selection.js +31 -0
  46. package/dist/components/table-of-contents/scrollspy-selection.js.map +1 -0
  47. package/dist/components/table-of-contents/scrollspy.d.ts +4 -0
  48. package/dist/components/table-of-contents/scrollspy.d.ts.map +1 -0
  49. package/dist/components/table-of-contents/scrollspy.js +178 -0
  50. package/dist/components/table-of-contents/scrollspy.js.map +1 -0
  51. package/dist/components/table-of-contents/types.d.ts +123 -0
  52. package/dist/components/table-of-contents/types.d.ts.map +1 -0
  53. package/dist/components/table-of-contents/types.js +1 -0
  54. package/dist/components/table-of-contents/types.js.map +1 -0
  55. package/dist/components/table-of-contents.d.ts +1 -0
  56. package/dist/components/table-of-contents.js +1 -0
  57. package/dist/styles/index.css +1 -1
  58. package/package.json +3 -2
  59. package/skills/uiify/SKILL.md +167 -0
  60. package/skills/uiify/scripts/lint.mjs +56 -0
  61. package/skills/uiify/scripts/static.mjs +95 -0
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: uiify
3
+ description: >-
4
+ Use when creating or improving a web page with @gunkin/uiify, especially when a project needs component
5
+ coverage, a distinctive visual direction, or UIify-aware design and implementation guidance.
6
+ disable-model-invocation: true
7
+ ---
8
+
9
+ # /uiify
10
+
11
+ Brief and flags: $ARGUMENTS
12
+
13
+ This skill writes only DESIGN.md and files under src/.
14
+
15
+ ## Stage chain
16
+
17
+ Run the stages R0 to R4 in order and never skip or reorder one. Each stage has a marker line, a result and, except R4, a gate line after it. Every marker, state and gate line is exact and alone on its own line. The first gate, for example, is `UIIFY:GATE R0>R1 auto`. How you output them depends on the flags above.
18
+
19
+ **Interactive (no `--auto`).** Follow this protocol for every stage:
20
+
21
+ 1. Output the stage marker as plain text before you call any tool for that stage.
22
+ 2. Do the stage's work; tools are allowed.
23
+ 3. Output the stage's result.
24
+ 4. Run the gate: ask once with AskUserQuestion ("Continue to the next stage?" with the options Continue and Stop). On Continue print `UIIFY:GATE <from>><to> asked` and go on. On Stop print `UIIFY:STOPPED` and end the run.
25
+
26
+ Do not call any tool for a later stage until the current stage's gate line is output. A stage whose marker was not output did not happen.
27
+
28
+ **Auto (`--auto`).** Ask nothing. Do all tool work first without commentary: read the project (R0) and create `DESIGN.md` when R2 says to. Then output the whole chain in one final message, in stage order: each stage's marker and result, with `UIIFY:GATE <from>><to> auto` between stages, ending as R4 says. Output nothing else before that message.
29
+
30
+ ### R0 activate
31
+
32
+ Marker `UIIFY:R0 activate`. Work: read the project. Does `package.json` list `@gunkin/uiify` under `dependencies` or `devDependencies`, and does `DESIGN.md` exist at the project root? Result: `UIIFY:STATE uiify=<yes|no> design=<found|missing>`.
33
+
34
+ ### R1 coverage
35
+
36
+ Marker `UIIFY:R1 coverage`. Work: split the brief into the page elements it needs and match each one to the index below by name or alias. Result: one line per element, `UIIFY:ELEMENT <name>|<binding>`. For an element the index lists, use its index name and its uiify column as the binding (a uiify component name or `gap`). For an element the index does not list, use a kebab-case name and the binding `unknown`.
37
+
38
+ ### R2 design
39
+
40
+ Marker `UIIFY:R2 design`. Work:
41
+
42
+ 1. Read `DESIGN.md` and reuse its recorded decisions. If missing, create exactly the skeleton below. Preserve the YAML `tokens` and every byte outside the managed block. If its structure or existing managed block is malformed, report the problem and leave it unchanged.
43
+ 2. Use the R1 bindings as the coverage map. The skill package intentionally contains no private element, law or composition catalog files; do not search for `references/elements/`, `references/laws/` or `references/compositions/` under the skill directory. R3 reads UIify's public component documentation when implementing mapped elements.
44
+ 3. Consult the `frontend-design` skill when it is available for design direction. Use `web-design-guidelines` only when the task asks for a review, and consult `vercel-react-best-practices` only for a relevant performance decision. These optional skills are references, not dependencies; if absent, use the built-in guidance below. UIify's public component and styling contracts take precedence over any conflicting advice.
45
+ 4. Ground the page in its actual subject, audience and primary job. Choose a visual direction that follows the brief and is specific to that subject. Plan a clear typography hierarchy, palette using the project's existing semantic roles, responsive layout, purposeful copy and one memorable signature detail. Use restraint around that signature; don't default to a gradient hero, repeated equal cards or decorative numbering without a reason. Preserve visible focus, keyboard-sized targets and reduced-motion behavior.
46
+ 5. Example: for a settings page, group fields by the decision the person is making, keep labels and validation close to each control, and give one `Button` the save action. Use the package's documented anatomy:
47
+
48
+ ```tsx
49
+ <Field.Root id="email" invalid={invalid} required>
50
+ <Field.Label fieldId="email" required>Email</Field.Label>
51
+ <Field.Control as="input" fieldId="email" type="email" invalid={invalid} required />
52
+ <Field.Error fieldId="email" invalid={invalid}>Enter a valid email address</Field.Error>
53
+ </Field.Root>
54
+ <Button type="submit">Save changes</Button>
55
+ ```
56
+
57
+ Don't add an unrelated marketing hero or make every field group an equally prominent card.
58
+ 6. Keep existing decisions and plan the page's element order, hierarchy, grouping, actions and visual direction as a decision with id `page-plan`, a concrete `decision`, `why` and `references`. Reuse an existing plan unless the brief requires an explicit revision; retain other decisions. Research only unresolved gaps or open decisions in a subagent, if both delegation and primary-source retrieval tools are available. Request a short summary with dated primary-source links. If those tools are unavailable, report unresolved gaps and keep them `proposed`. Record dates alongside the source URLs in `sources`. Only explicit owner approval can make a gap `accepted`; `--auto` never grants approval. Follow owner prose and report conflicts with the brief; interactive mode can clarify at the normal gate, while `--auto` preserves recorded choices.
59
+ 7. Store state only in one JSON block beneath `## Decisions`, delimited by an opening `<!-- uiify:state -->` line and a closing `<!-- /uiify:state -->` line. Its exact shape is `{ "version": 1, "elements": ["R1-name"], "decisions": [{ "id": "page-plan", "decision": "concrete page plan and visual direction", "why": "reason", "references": ["public UIify docs or dated source URLs"] }], "gaps": [{ "element": "gap-name", "status": "proposed", "sources": [] }] }`. Empty lists are allowed; IDs and elements are unique; strings are nonempty. Do not put prose or fences inside the JSON block. Reject duplicate, unclosed, misplaced or invalid blocks. Insert the block immediately after the Decisions heading's newline, without changing surrounding bytes; replace only an existing block and its following newline. If state is unchanged, leave the whole file byte-identical. Keep existing gap acceptance and sources; do not infer approval from research.
60
+
61
+ Result: summarize the stored page plan, reused decisions and unresolved gaps, including research limitations.
62
+
63
+ ```markdown
64
+ ---
65
+ tokens: {}
66
+ ---
67
+
68
+ # Design
69
+
70
+ ## Decisions
71
+
72
+ ## Gaps
73
+ ```
74
+
75
+ ### R3 build
76
+
77
+ Marker `UIIFY:R3 build`. Work: if `src/App.tsx` exists, write the page into it (new components go under `src/`), following the R1 table and the stored `page-plan` in `DESIGN.md`. Read `node_modules/@gunkin/uiify/agents/index.md` once and adapt a relevant documented example when it matches the page; for each mapped component, read `node_modules/@gunkin/uiify/agents/reference/components/<name>.md`, then import it from `@gunkin/uiify/components/<name>`. Before rendering an export, inspect its Runtime exports and the Types/declaration file for component shape; use the explicit `NameRoot` export when available. A namespace export is not itself a JSX component; use its `.Root` or the explicit root export. For a `gap` or `unknown` element, use semantic native HTML and UIify's public styling contract, and consult public design-system or platform guidance for unresolved behavior when retrieval is available. Write no file outside `src/` and leave `DESIGN.md` as it is. If `src/App.tsx` does not exist, write nothing. Result: one sentence naming what you wrote and where, or where the page would go.
78
+
79
+ ### R4 verify
80
+
81
+ Marker `UIIFY:R4 verify`. Work: when commands are permitted and an execution tool is available, run `node <this-skill-directory>/scripts/lint.mjs <project-root>` and report its actual output. The script runs only the static subset enabled by accepted laws; no enabled checks is not proof of accessibility. Missing TypeScript/parser or another execution failure is unavailable, never a pass. If the consuming task prohibits commands, report "lint not run" with that reason; if no execution tool is available, report that limitation. When a browser tool is available and permitted, inspect the page and requested interactions without creating screenshot artifacts; otherwise report that browser verification was not run. Repair findings only under `src/` in at most two rounds, rerunning available checks after each. Do not edit DESIGN.md during repairs. Result: report executed checks, unavailable checks with reasons, repairs and remaining issues; never claim a check passed without running it. Then print `UIIFY:DONE` and stop.
82
+
83
+ ## Hard rules
84
+
85
+ - Prefer published UIify component documentation over memory when implementing a mapped element.
86
+ - Use a uiify component whenever the index names one for the element.
87
+ - Ground visual choices in the brief and subject; don't impose one preferred palette or page template.
88
+ - Apply optional frontend skills only when available and relevant; UIify's public contracts override generic advice.
89
+ - Match elements only against the index: never invent a binding and never use an alias as the element name.
90
+ - Run every stage in this conversation and never use context: fork.
91
+ - Write only `DESIGN.md` and files under `src/`; R2 changes only its managed state block, R4 repairs only `src/` in at most two rounds.
92
+
93
+ ## Index
94
+
95
+ One line per element: `element|aliases|uiify component or gap|` (the final field is reserved and empty).
96
+
97
+ ```text
98
+ accordion|faq-accordion,disclosure|accordion|
99
+ alert-modal|confirmation-dialog,alert-dialog|alert-modal|
100
+ avatar|profile-picture|gap|
101
+ badge|chip,tag|gap|
102
+ bar-chart|column-chart|gap|
103
+ breadcrumb|breadcrumbs|breadcrumb|
104
+ button|cta-button,icon-button,primary-button|button|
105
+ candle-chart|candlestick-chart|candle-chart|
106
+ card|content-card|gap|
107
+ carousel|testimonial-carousel,image-slider|carousel|
108
+ checkbox||checkbox|
109
+ code-block|code-snippet|gap|
110
+ collapsible|expandable-section|collapsible|
111
+ color-picker||color-picker|
112
+ combobox|autocomplete,typeahead|combobox|
113
+ command-palette|spotlight-search|gap|
114
+ context-menu|right-click-menu|context-menu|
115
+ cookie-banner|cookie-consent|gap|
116
+ date-picker|calendar-picker|gap|
117
+ divider|separator|gap|
118
+ drawer|sheet,off-canvas-menu|drawer|
119
+ dropdown-menu|menu|dropdown-menu|
120
+ empty-state|no-results|gap|
121
+ feature-grid|feature-cards,feature-list|gap|
122
+ field|form-field|field|
123
+ file-upload|file-input,dropzone|file-upload|
124
+ footer|site-footer|gap|
125
+ form||form|
126
+ header|navbar,top-nav|header|
127
+ hero|hero-section,hero-banner|gap|
128
+ hover-card||hover-card|
129
+ image-gallery|gallery,lightbox|gap|
130
+ inline-alert|banner-alert,callout|gap|
131
+ input|text-input|input|
132
+ line-chart|sparkline|line-chart|
133
+ logo-wall|logo-cloud,customer-logos|gap|
134
+ modal|dialog|modal|
135
+ number-field|number-input|number-field|
136
+ otp-input|pin-input|otp-input|
137
+ pagination||pagination|
138
+ password-input|show-hide-password|gap|
139
+ popup|popover|popup|
140
+ pricing-table|pricing,plan-cards,pricing-plans|gap|
141
+ progress-bar|progress,linear-progress|gap|
142
+ radio-group|radio|radio-group|
143
+ rating|star-rating|rating|
144
+ search-input|search-box|gap|
145
+ segmented-control||segmented-control|
146
+ select|native-select|select|
147
+ sidebar|sidebar-nav,side-nav|gap|
148
+ skeleton|skeleton-loader|skeleton|
149
+ slider|range-slider|slider|
150
+ spinner|loading-indicator|spinner|
151
+ stats-bar|stat-card,metrics|gap|
152
+ stepper|step-indicator,wizard-steps|gap|
153
+ switch|toggle-switch|switch|
154
+ table|data-table,comparison-table|table|
155
+ table-of-contents|toc,in-page-nav|table-of-contents|
156
+ tabs|tab-navigation|tabs|
157
+ tags-input|token-input|tags-input|
158
+ testimonial|testimonials,quote-card|gap|
159
+ textarea||textarea|
160
+ timeline||gap|
161
+ toast|snackbar,toast-notification|toast|
162
+ toggle|toggle-button|toggle|
163
+ toggle-group|button-group|toggle-group|
164
+ toolbar||toolbar|
165
+ tooltip||tooltip|
166
+ virtual-scroll|virtualized-list|virtual-scroll|
167
+ ```
@@ -0,0 +1,56 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { createRequire } from "node:module";
3
+ import { extname, join, relative, resolve, sep } from "node:path";
4
+ import { staticChecks } from "./static.mjs";
5
+
6
+ const enabledIds = new Set(["no-positive-tabindex","button-type"]);
7
+ const excluded = new Set(["node_modules", ".git", ".next", ".turbo", "dist", "build", "coverage"]);
8
+
9
+ async function sources(directory) {
10
+ const files = [];
11
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
12
+ const path = join(directory, entry.name);
13
+ if (entry.isDirectory() && !excluded.has(entry.name)) files.push(...(await sources(path)));
14
+ else if (
15
+ entry.isFile() &&
16
+ [".tsx", ".jsx", ".ts", ".js", ".mts", ".cts", ".mjs", ".cjs"].includes(extname(path))
17
+ )
18
+ files.push(path);
19
+ }
20
+ return files.sort();
21
+ }
22
+
23
+ async function lint() {
24
+ const root = resolve(process.argv[2] ?? ".");
25
+ let typescript;
26
+ try {
27
+ typescript = createRequire(join(root, "package.json"))("typescript");
28
+ } catch (error) {
29
+ if (error.code === "MODULE_NOT_FOUND")
30
+ throw new Error(
31
+ "TypeScript parser is not installed in the consumer project; install typescript to run lint.",
32
+ );
33
+ throw error;
34
+ }
35
+ const checks = staticChecks.filter((check) => enabledIds.has(check.id));
36
+ let count = 0;
37
+ for (const path of await sources(root)) {
38
+ const source = await readFile(path, "utf8");
39
+ const file = relative(root, path).split(sep).join("/");
40
+ for (const check of checks) {
41
+ for (const finding of check.run({ source, file, typescript }).findings) {
42
+ console.log(`${finding.file}:${finding.line} ${finding.rule}: ${finding.message}`);
43
+ count += 1;
44
+ }
45
+ }
46
+ }
47
+ console.log(`${count} findings (${checks.length} accepted static checks).`);
48
+ process.exitCode = count > 0 ? 1 : 0;
49
+ }
50
+
51
+ try {
52
+ await lint();
53
+ } catch (error) {
54
+ console.error(error.message);
55
+ process.exitCode = 2;
56
+ }
@@ -0,0 +1,95 @@
1
+ /** Parser-backed source facts only. Kept dependency-free so sync can copy it into the skill. */
2
+ function parse({ source, file, typescript }) {
3
+ if (!typescript?.createSourceFile) {
4
+ throw Object.assign(new Error("TypeScript parser is not installed in the consumer project."), {
5
+ code: "MISSING_TYPESCRIPT",
6
+ });
7
+ }
8
+ const tree = typescript.createSourceFile(file, source, typescript.ScriptTarget.Latest, true);
9
+ if (tree.parseDiagnostics.length > 0) {
10
+ const diagnostic = tree.parseDiagnostics[0];
11
+ const { line } = tree.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
12
+ throw Object.assign(
13
+ new Error(
14
+ `${file}:${line + 1}: cannot parse source: ${typescript.flattenDiagnosticMessageText(diagnostic.messageText, " ")}`,
15
+ ),
16
+ { code: "PARSE_ERROR" },
17
+ );
18
+ }
19
+ return tree;
20
+ }
21
+
22
+ function attributes(node, typescript) {
23
+ return node.attributes.properties.filter((attribute) => typescript.isJsxAttribute(attribute));
24
+ }
25
+
26
+ function positiveLiteral(attribute, typescript) {
27
+ let value = attribute.initializer;
28
+ if (value && typescript.isJsxExpression(value)) value = value.expression;
29
+ while (value && typescript.isParenthesizedExpression(value)) value = value.expression;
30
+ if (
31
+ value &&
32
+ typescript.isPrefixUnaryExpression(value) &&
33
+ value.operator === typescript.SyntaxKind.PlusToken
34
+ ) {
35
+ value = value.operand;
36
+ }
37
+ return Boolean(
38
+ value &&
39
+ (typescript.isNumericLiteral(value) || typescript.isStringLiteral(value)) &&
40
+ Number(value.text) > 0,
41
+ );
42
+ }
43
+
44
+ function sourceCheck(id, law, message, violates) {
45
+ return {
46
+ id,
47
+ kind: "static",
48
+ law,
49
+ run(input) {
50
+ const tree = parse(input);
51
+ const { file, typescript } = input;
52
+ const findings = [];
53
+ const visit = (node) => {
54
+ if (
55
+ (typescript.isJsxOpeningElement(node) || typescript.isJsxSelfClosingElement(node)) &&
56
+ typescript.isIdentifier(node.tagName) &&
57
+ /^[a-z][a-z0-9-]*$/.test(node.tagName.text) &&
58
+ violates(node.tagName.text, attributes(node, typescript), typescript)
59
+ ) {
60
+ findings.push({
61
+ file,
62
+ line: tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1,
63
+ rule: id,
64
+ message,
65
+ });
66
+ }
67
+ typescript.forEachChild(node, visit);
68
+ };
69
+ visit(tree);
70
+ return { pass: findings.length === 0, detail: `${findings.length} ${id} findings`, findings };
71
+ },
72
+ };
73
+ }
74
+
75
+ export const staticChecks = [
76
+ sourceCheck(
77
+ "no-positive-tabindex",
78
+ "accessibility",
79
+ "Use zero or negative tabIndex instead of a positive literal.",
80
+ (_, attrs, typescript) =>
81
+ attrs.some((attr) => attr.name.text === "tabIndex" && positiveLiteral(attr, typescript)),
82
+ ),
83
+ sourceCheck(
84
+ "button-type",
85
+ "error-prevention",
86
+ "Give the intrinsic button an explicit type.",
87
+ (tag, attrs) => tag === "button" && !attrs.some((attr) => attr.name.text === "type"),
88
+ ),
89
+ sourceCheck(
90
+ "image-alt",
91
+ "accessibility",
92
+ "Give the intrinsic image an alt attribute (empty for decoration).",
93
+ (tag, attrs) => tag === "img" && !attrs.some((attr) => attr.name.text === "alt"),
94
+ ),
95
+ ];