@tonycodes-ui/mortar 0.0.0-stage → 0.11.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 (104) hide show
  1. package/AGENTS.md +10 -0
  2. package/CHANGELOG.md +276 -0
  3. package/CONTRIBUTING.md +48 -0
  4. package/DESIGN.md +51 -0
  5. package/LICENSE +21 -0
  6. package/README.md +155 -2
  7. package/TESTING.md +83 -0
  8. package/baseline-policy.json +16 -0
  9. package/bin/mortar.mjs +258 -0
  10. package/catalog.json +9703 -0
  11. package/cli/audit.mjs +943 -0
  12. package/cli/data.mjs +176 -0
  13. package/cli/mcp.mjs +216 -0
  14. package/cli/migrations.mjs +128 -0
  15. package/cli/packet.mjs +267 -0
  16. package/cli/search.mjs +246 -0
  17. package/cli/tools.mjs +229 -0
  18. package/design-principles.json +30 -0
  19. package/dist/agent-data.json +1 -0
  20. package/dist/application.d.ts +340 -0
  21. package/dist/application.js +240 -0
  22. package/dist/auth.d.ts +121 -0
  23. package/dist/auth.js +97 -0
  24. package/dist/brand.d.ts +69 -0
  25. package/dist/brand.js +266 -0
  26. package/dist/calendar.d.ts +75 -0
  27. package/dist/calendar.js +310 -0
  28. package/dist/choice.d.ts +36 -0
  29. package/dist/choice.js +36 -0
  30. package/dist/composer.d.ts +28 -0
  31. package/dist/composer.js +47 -0
  32. package/dist/display.d.ts +136 -0
  33. package/dist/display.js +75 -0
  34. package/dist/docs.d.ts +145 -0
  35. package/dist/docs.js +141 -0
  36. package/dist/ecommerce.d.ts +261 -0
  37. package/dist/ecommerce.js +177 -0
  38. package/dist/feedback.d.ts +80 -0
  39. package/dist/feedback.js +98 -0
  40. package/dist/foundation.d.ts +42 -0
  41. package/dist/foundation.js +121 -0
  42. package/dist/heading.d.ts +5 -0
  43. package/dist/heading.js +9 -0
  44. package/dist/icons.d.ts +50 -0
  45. package/dist/icons.js +70 -0
  46. package/dist/index.d.ts +34 -0
  47. package/dist/index.js +17 -0
  48. package/dist/inspection.d.ts +24 -0
  49. package/dist/inspection.js +14 -0
  50. package/dist/internal.d.ts +492 -0
  51. package/dist/internal.js +273 -0
  52. package/dist/layout.d.ts +107 -0
  53. package/dist/layout.js +66 -0
  54. package/dist/marketing-sections.d.ts +161 -0
  55. package/dist/marketing-sections.js +58 -0
  56. package/dist/marketing.d.ts +254 -0
  57. package/dist/marketing.js +150 -0
  58. package/dist/navigation.d.ts +35 -0
  59. package/dist/navigation.js +44 -0
  60. package/dist/primitives.d.ts +258 -0
  61. package/dist/primitives.js +244 -0
  62. package/dist/showcase.d.ts +29 -0
  63. package/dist/showcase.js +10 -0
  64. package/dist/workspace.d.ts +80 -0
  65. package/dist/workspace.js +48 -0
  66. package/docs/agents.md +127 -0
  67. package/docs/auth-flows.md +71 -0
  68. package/docs/migrating-to-0.6.md +94 -0
  69. package/docs/theming.md +170 -0
  70. package/package.json +94 -4
  71. package/skills/mortar/SKILL.md +36 -0
  72. package/src/application.css +1487 -0
  73. package/src/application.tsx +1413 -0
  74. package/src/auth.tsx +452 -0
  75. package/src/brand.ts +335 -0
  76. package/src/calendar.css +468 -0
  77. package/src/calendar.tsx +868 -0
  78. package/src/choice.tsx +240 -0
  79. package/src/composer.tsx +158 -0
  80. package/src/data.css +690 -0
  81. package/src/display.tsx +452 -0
  82. package/src/docs.css +704 -0
  83. package/src/docs.tsx +737 -0
  84. package/src/ecommerce.css +810 -0
  85. package/src/ecommerce.tsx +1033 -0
  86. package/src/feedback.tsx +388 -0
  87. package/src/foundation.css +1867 -0
  88. package/src/foundation.tsx +227 -0
  89. package/src/heading.ts +13 -0
  90. package/src/icons.tsx +237 -0
  91. package/src/index.ts +230 -0
  92. package/src/inspection.tsx +91 -0
  93. package/src/internal.tsx +614 -0
  94. package/src/layout.tsx +342 -0
  95. package/src/marketing-sections.tsx +621 -0
  96. package/src/marketing.css +2400 -0
  97. package/src/marketing.tsx +1156 -0
  98. package/src/navigation.tsx +118 -0
  99. package/src/overlays.css +350 -0
  100. package/src/primitives.tsx +1135 -0
  101. package/src/showcase.tsx +120 -0
  102. package/src/tokens.css +449 -0
  103. package/src/workspace.tsx +333 -0
  104. package/styles.css +56 -0
package/cli/tools.mjs ADDED
@@ -0,0 +1,229 @@
1
+ // The MCP tools. Each handler takes validated arguments and returns a plain
2
+ // object (serialised as JSON text) or a string (returned as text).
3
+ import { GUIDES } from "./data.mjs";
4
+ import { searchComponents } from "./search.mjs";
5
+ import { componentDetail, buildPacket } from "./packet.mjs";
6
+ import { auditApp, RULES } from "./audit.mjs";
7
+
8
+ export class ToolError extends Error {}
9
+
10
+ const componentNotFound = (data, name) =>
11
+ new ToolError(
12
+ `No component named "${name}" in ${data.packageName} ${data.version}.` +
13
+ ` Closest matches: ${
14
+ searchComponents(data, { query: name, limit: 5 })
15
+ .map((r) => r.name)
16
+ .join(", ") || "none"
17
+ }. Use search_components or list_kits to find one.`,
18
+ );
19
+
20
+ export const TOOLS = [
21
+ {
22
+ name: "search_components",
23
+ title: "Search Mortar components",
24
+ description:
25
+ 'Search the installed Mortar UI kit (@tonycodes-ui/mortar, React 19 + Tailwind 4) for components by purpose, e.g. "sign in", "data table with sorting", "pricing". Returns ranked matches with name, kit, category, a one-line description and capabilities. Use it before writing any UI so you reuse the kit instead of building your own; then call get_component or get_build_packet with the names you pick. Works offline against the installed version.',
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ query: {
30
+ type: "string",
31
+ description:
32
+ "What the UI needs to do, or a component name. Plain words work best.",
33
+ },
34
+ kit: {
35
+ type: "string",
36
+ description:
37
+ "Only this kit: foundation, application, marketing, docs or ecommerce.",
38
+ },
39
+ category: {
40
+ type: "string",
41
+ description:
42
+ 'Only this category id or title from list_kits, e.g. "auth" or "Lists and tables".',
43
+ },
44
+ limit: {
45
+ type: "integer",
46
+ minimum: 1,
47
+ maximum: 50,
48
+ default: 10,
49
+ description: "Maximum number of results.",
50
+ },
51
+ },
52
+ required: ["query"],
53
+ additionalProperties: false,
54
+ },
55
+ annotations: { readOnlyHint: true, openWorldHint: false },
56
+ run: (data, args) => ({
57
+ package: data.packageName,
58
+ version: data.version,
59
+ results: searchComponents(data, args).map(({ matched, ...r }) => r),
60
+ }),
61
+ },
62
+ {
63
+ name: "get_component",
64
+ title: "Get a Mortar component contract",
65
+ description:
66
+ "The full contract of one Mortar component: description, import line, every prop with type, required flag and default, usage / avoid / accessibility notes, the components it renders (dependencies), its data-m-component marker, tested states, kit and category, page templates that use it, and a runnable JSX example from the playground. Use the exact prop names and union values it lists; do not guess props.",
67
+ inputSchema: {
68
+ type: "object",
69
+ properties: {
70
+ name: {
71
+ type: "string",
72
+ description: 'Component export name, e.g. "DataTable" (any case).',
73
+ },
74
+ },
75
+ required: ["name"],
76
+ additionalProperties: false,
77
+ },
78
+ annotations: { readOnlyHint: true, openWorldHint: false },
79
+ run: (data, { name }) => {
80
+ const c = data.find(name);
81
+ if (!c) throw componentNotFound(data, name);
82
+ return componentDetail(data, c);
83
+ },
84
+ },
85
+ {
86
+ name: "get_build_packet",
87
+ title: "Get a Mortar build packet",
88
+ description:
89
+ 'Everything needed to build a screen with Mortar in one response. Pass `components` (export names), a free-text `brief` ("an admin screen listing invoices with filters"), or both; at least one is required. Returns: the chosen components with full contracts and examples (for a brief, why each was chosen, starting from the purpose shell: AppShell, MarketingHeader, DocsShell or AuthLayout), the dependency closure they render, the import line, install command, Tailwind CSS setup, ThemeProvider setup, the design principles and API conventions, and the playground page templates that compose the same components. Start composing from this packet.',
90
+ inputSchema: {
91
+ type: "object",
92
+ properties: {
93
+ components: {
94
+ type: "array",
95
+ items: { type: "string" },
96
+ description:
97
+ 'Component export names, e.g. ["AppShell", "DataTable"].',
98
+ },
99
+ brief: {
100
+ type: "string",
101
+ description:
102
+ "What you are building, in plain words. Components are chosen by search and purpose.",
103
+ },
104
+ },
105
+ additionalProperties: false,
106
+ },
107
+ annotations: { readOnlyHint: true, openWorldHint: false },
108
+ run: (data, args) => {
109
+ if (!args.components?.length && !args.brief?.trim())
110
+ throw new ToolError("Pass components (a non-empty list) or a brief.");
111
+ return buildPacket(data, args);
112
+ },
113
+ },
114
+ {
115
+ name: "get_guide",
116
+ title: "Read a Mortar guide",
117
+ description: `Read one of Mortar's guides as markdown, from the installed package. Topics: ${Object.entries(
118
+ GUIDES,
119
+ )
120
+ .map(([k, g]) => `${k} (${g.summary.replace(/\.$/, "")})`)
121
+ .join("; ")}.`,
122
+ inputSchema: {
123
+ type: "object",
124
+ properties: {
125
+ topic: {
126
+ type: "string",
127
+ enum: Object.keys(GUIDES),
128
+ description: "The guide to read.",
129
+ },
130
+ },
131
+ required: ["topic"],
132
+ additionalProperties: false,
133
+ },
134
+ annotations: { readOnlyHint: true, openWorldHint: false },
135
+ run: (data, { topic }) => {
136
+ const text = data.guide(topic);
137
+ if (!text)
138
+ throw new ToolError(
139
+ `The guide "${topic}" is not in this installation of ${data.packageName}.`,
140
+ );
141
+ return text;
142
+ },
143
+ },
144
+ {
145
+ name: "list_kits",
146
+ title: "List Mortar kits",
147
+ description:
148
+ "The whole Mortar catalogue as kits → categories → components (Foundation, Application UI, Marketing UI, Docs UI, Ecommerce), plus the playground page templates. Use it to browse when you do not know what to search for.",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {},
152
+ additionalProperties: false,
153
+ },
154
+ annotations: { readOnlyHint: true, openWorldHint: false },
155
+ run: (data) => ({
156
+ package: data.packageName,
157
+ version: data.version,
158
+ kits: data.agent.taxonomy.kits.map((k) => ({
159
+ id: k.id,
160
+ title: k.title,
161
+ description: k.description,
162
+ categories: k.categories.map((c) => ({
163
+ id: c.id,
164
+ title: c.title,
165
+ components: c.components,
166
+ })),
167
+ })),
168
+ templates: data.agent.templates.map((t) => ({
169
+ id: t.id,
170
+ title: t.title,
171
+ kind: t.kind,
172
+ description: t.description,
173
+ })),
174
+ }),
175
+ },
176
+ {
177
+ name: "audit_app",
178
+ title: "Audit an app's Mortar usage",
179
+ description: `Static, read-only audit of an application directory that uses Mortar; it never modifies files. Reports findings with file, line, rule, message and a suggested fix. Rules: ${Object.entries(
180
+ RULES,
181
+ )
182
+ .map(([k, v]) => `${k} (${v.replace(/\.$/, "")})`)
183
+ .join(
184
+ "; ",
185
+ )}. Props and imports are checked against the installed version's contracts, with 0.6 rename suggestions. A lightweight scanner, not a type checker: props passed by spread and wrapped components are not checked, so also run the app's own typecheck and build.`,
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ path: {
190
+ type: "string",
191
+ description:
192
+ "The application directory (where its package.json is). Relative paths resolve from the server's working directory. Defaults to it.",
193
+ },
194
+ },
195
+ additionalProperties: false,
196
+ },
197
+ annotations: { readOnlyHint: true, openWorldHint: false },
198
+ run: (data, { path = "." }) => auditApp(path, { data }),
199
+ },
200
+ ];
201
+
202
+ const typeOf = (v) =>
203
+ Array.isArray(v) ? "array" : Number.isInteger(v) ? "integer" : typeof v;
204
+
205
+ /** Validate arguments against a tool's (flat) input schema. Returns an error message or undefined. */
206
+ export function validateArguments(tool, args) {
207
+ if (args === undefined) args = {};
208
+ if (typeOf(args) !== "object" || args === null)
209
+ return "arguments must be an object";
210
+ const { properties = {}, required = [] } = tool.inputSchema;
211
+ for (const key of required)
212
+ if (args[key] === undefined) return `missing required argument "${key}"`;
213
+ for (const [key, value] of Object.entries(args)) {
214
+ const schema = properties[key];
215
+ if (!schema) return `unknown argument "${key}"`;
216
+ const t = typeOf(value);
217
+ if (schema.type === "integer" ? t !== "integer" : t !== schema.type)
218
+ return `"${key}" must be ${schema.type === "integer" ? "an integer" : `a ${schema.type}`}`;
219
+ if (schema.enum && !schema.enum.includes(value))
220
+ return `"${key}" must be one of ${schema.enum.join(", ")}`;
221
+ if (schema.items && value.some((v) => typeOf(v) !== schema.items.type))
222
+ return `"${key}" must contain only ${schema.items.type}s`;
223
+ if (schema.minimum !== undefined && value < schema.minimum)
224
+ return `"${key}" must be at least ${schema.minimum}`;
225
+ if (schema.maximum !== undefined && value > schema.maximum)
226
+ return `"${key}" must be at most ${schema.maximum}`;
227
+ }
228
+ return undefined;
229
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.0",
4
+ "principles": [
5
+ {
6
+ "id": "shell-first",
7
+ "instruction": "Choose the shared purpose shell before composing a page. Use AppShell for application screens and MarketingHeader for public pages. Document deliberate standalone exceptions."
8
+ },
9
+ {
10
+ "id": "page-pattern-reuse",
11
+ "instruction": "Reuse complete page patterns: navigation, headings, filters, list/detail workspaces, states and action placement. Reusing controls alone is insufficient."
12
+ },
13
+ {
14
+ "id": "project-agnostic-chrome",
15
+ "instruction": "Keep product names, routes, user data, permissions and actions in props and slots. Shared chrome must remain project agnostic. The marketing header logo is a slot. Every component root exposes data-m-component with the component's name for tests and consumers. It is not a styling hook."
16
+ },
17
+ {
18
+ "id": "central-fixes",
19
+ "instruction": "Contribute missing patterns centrally. Do not duplicate source, create repeated local wrappers or apply consumer CSS overrides."
20
+ },
21
+ {
22
+ "id": "semantic-themes",
23
+ "instruction": "Use semantic tokens; keep brand separate from appearance and feedback colours. Verify every supported theme. Accent is a brand token, separate from ink, surface, and the feedback colours. Brands declare only the --m-brand-* contract (--m-brand-l-*, --m-brand-d-* and the brand fonts); each appearance resolves --m-accent, --m-on-accent, --m-accent-fill, the surfaces and their --m-inv-* twins from it, so every brand gets both without brand-specific CSS. A runtime brand comes from ThemeProvider brandTokens. CallToAction and a featured Pricing card use the page surface unless tone=\"inverse\" opts into the inverse token remap. ThemeProvider scope=\"container\" paints those tokens on a wrapper and leaves the document unchanged. The default scope is document."
24
+ },
25
+ {
26
+ "id": "interaction-quality",
27
+ "instruction": "Verify responsive layout, accessible names, keyboard operation, focus and loading/empty/error states. Automated checks alone cannot prove visual quality. A plan that is not available exposes a status and does not offer a purchase link. Column titles accept an explicit heading level so a page can keep a single h1."
28
+ }
29
+ ]
30
+ }