create-ailk 0.7.0 → 0.8.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 (169) hide show
  1. package/README.md +44 -10
  2. package/component-catalog.json +520 -5
  3. package/component-catalog.md +9 -6
  4. package/dist/cli.js +22 -24
  5. package/dist/lib/extract-module-bundle.js +5 -1
  6. package/dist/lib/module-license-gate.d.ts +79 -0
  7. package/dist/lib/module-license-gate.js +160 -12
  8. package/dist/lib/module-tier.d.ts +45 -16
  9. package/dist/lib/module-tier.js +76 -0
  10. package/dist/lib/pro-callout.js +10 -2
  11. package/dist/parse-args.d.ts +78 -10
  12. package/dist/parse-args.js +204 -14
  13. package/dist/programmatic.d.ts +39 -9
  14. package/dist/programmatic.js +163 -25
  15. package/dist/site-roster.js +11 -6
  16. package/dist/starter-grid.d.ts +216 -0
  17. package/dist/starter-grid.js +449 -0
  18. package/dist/surfaces.d.ts +4 -4
  19. package/dist/surfaces.js +102 -43
  20. package/dist/template-manifest.d.ts +36 -17
  21. package/dist/template-manifest.js +121 -367
  22. package/dist/template-names.d.ts +14 -12
  23. package/dist/template-names.js +13 -17
  24. package/package.json +3 -3
  25. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -1
  26. package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
  27. package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
  28. package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
  29. package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
  30. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
  31. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
  32. package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
  33. package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
  34. package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
  35. package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
  36. package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
  37. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
  38. package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
  39. package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
  40. package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
  41. package/site-templates/designs/standard/DESIGN.md +5 -0
  42. package/site-templates/marketing-site/DESIGN.md +5 -0
  43. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +89 -25
  44. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +35 -14
  45. package/site-templates/marketing-site-free/DESIGN.md +5 -0
  46. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +95 -29
  47. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +74 -44
  48. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
  49. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
  50. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +17 -29
  51. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -1
  52. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  53. package/site-templates/saas-product/DESIGN.md +5 -0
  54. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
  55. package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +96 -76
  56. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -0
  57. package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
  58. package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
  59. package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
  60. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  61. package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
  62. package/site-templates/{simple-service → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
  63. package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
  64. package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
  65. package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
  66. package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
  67. package/site-templates/shapes/local-business-service/provenance.json +4 -0
  68. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +362 -0
  69. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -0
  70. package/site-templates/shapes/organization-service/ailk-template.json +10 -0
  71. package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
  72. package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
  73. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  74. package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
  75. package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  76. package/site-templates/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
  77. package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
  78. package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
  79. package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
  80. package/site-templates/shapes/organization-service/provenance.json +4 -0
  81. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
  82. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
  83. package/site-templates/shapes/person-service/ailk-template.json +10 -0
  84. package/site-templates/shapes/person-service/content/_site.mdx +85 -0
  85. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
  86. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
  87. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  88. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
  89. package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  90. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
  91. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
  92. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
  93. package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
  94. package/site-templates/shapes/person-service/provenance.json +4 -0
  95. package/templates/.claude/skills/README.md +3 -2
  96. package/templates/.claude/skills/add-page/references/page-types.md +4 -0
  97. package/templates/.claude/skills/setup-database/SKILL.md +156 -0
  98. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
  99. package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
  100. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +19 -28
  101. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
  102. package/templates/DESIGN.md +5 -0
  103. package/templates/README.md +46 -38
  104. package/templates/apps/api/CLAUDE.md +3 -2
  105. package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
  106. package/templates/apps/api/src/config/index.ts +35 -16
  107. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
  108. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
  109. package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
  110. package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
  111. package/templates/apps/api/src/openapi/spec.ts +63 -12
  112. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
  113. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
  114. package/templates/apps/api/src/routes/leads/index.ts +46 -1
  115. package/templates/apps/api/src/server.ts +7 -0
  116. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
  117. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
  118. package/templates/apps/api/src/services/lead-routing.ts +140 -30
  119. package/templates/apps/api/src/services/leads-inbox.ts +48 -11
  120. package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
  121. package/templates/apps/mcp/src/tools/create_project.ts +21 -6
  122. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
  123. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
  124. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
  125. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
  126. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
  127. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
  128. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
  129. package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
  130. package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
  131. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
  132. package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
  133. package/templates/apps/web/app/[locale]/layout.tsx +37 -5
  134. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
  135. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
  136. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
  137. package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
  138. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
  139. package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
  140. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +18 -3
  141. package/templates/apps/web/lib/nav-config.ts +48 -36
  142. package/templates/apps/web/lib/site-identity.ts +14 -0
  143. package/templates/apps/web/messages/en.json +28 -2
  144. package/templates/content/_site.mdx +4 -2
  145. package/templates/content/en/BlogList/blog.mdx +37 -16
  146. package/templates/content/en/ContactPage/contact.mdx +14 -44
  147. package/templates/content/en/HomePage/home.mdx +8 -16
  148. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  149. package/templates/content/en/UseCasesList/use-cases.mdx +6 -6
  150. package/templates/database/CHANGELOG.md +7 -0
  151. package/templates/database/package.json +1 -1
  152. package/versions.json +33 -33
  153. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
  154. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
  155. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
  156. package/site-templates/simple-service/DESIGN.md +0 -162
  157. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
  158. package/site-templates/simple-service/ailk-template.json +0 -11
  159. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
  160. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
  161. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
  162. package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
  163. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
  164. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
  165. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
  166. package/site-templates/simple-service/provenance.json +0 -4
  167. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
  168. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
  169. package/templates/content/en/SoftwareProduct/product.mdx +0 -175
@@ -1,40 +0,0 @@
1
- ---
2
- type: "Service"
3
- slug: "seasonal-cleanup"
4
- title: "{Seasonal cleanup}"
5
- description: "{One line on what this job is and who it suits. Replace before you launch.}"
6
- price: 289
7
- priceCurrency: "USD"
8
- provider:
9
- name: "{Your Company}"
10
- schema:
11
- serviceType: "{Seasonal cleanup}"
12
- areaServed: "{Your service area}"
13
- ---
14
-
15
- # \{Seasonal cleanup\}
16
-
17
- \{Open with the situation a reader is in when they come looking for this. Two or
18
- three sentences, no more — the detail belongs under the headings below.\}
19
-
20
- ## What it covers
21
-
22
- - \{The first thing you actually do\}
23
- - \{The second thing you actually do\}
24
- - \{The third thing you actually do\}
25
-
26
- ## How it runs
27
-
28
- \{One paragraph on the shape of the visit: how it is scheduled, how long it
29
- takes, who turns up, and what you need from the customer beforehand.\}
30
-
31
- ## What you end up with
32
-
33
- \{One paragraph on the result. Be concrete — describe the yard when the crew
34
- drives away.\}
35
-
36
- ## What it costs
37
-
38
- \{The price in this page's frontmatter is a PLACEHOLDER. It is what the site
39
- publishes as a schema.org `Offer`, so an agent reading this page quotes it —
40
- replace it with your real number before you launch.\}
@@ -1,4 +0,0 @@
1
- {
2
- "template": "simple-service",
3
- "requires": ["marketing", "scheduling", "commerce"]
4
- }
@@ -1,211 +0,0 @@
1
- "use client";
2
-
3
- /**
4
- * ContactForm — client component that wires the contact form to
5
- * captureLeadAction via useFormState.
6
- *
7
- * Validation errors render inline next to each field.
8
- * Success shows a confirmation message.
9
- * Server errors show a generic fallback message.
10
- *
11
- * Honeypot + rate-limit enforcement lives in apps/api; no duplication here.
12
- *
13
- * `initialValues` (#4534, epic #4521's integration leg): optional prefill
14
- * for name/email/message, set via `defaultValue` (the fields stay
15
- * uncontrolled — unchanged for the no-prop call site). Lets
16
- * `ConversationalIntakeWrapper` carry over whatever the visitor already
17
- * typed into the conversation before Skip (or a failed auto-submit)
18
- * dropped them here, instead of asking them to retype it.
19
- *
20
- * Consent (#6466): a real checkbox named by a `ConsentStatement` whose
21
- * "Terms" links to the locale-aware `/terms` route. It starts unticked and
22
- * the submit button stays disabled until it is ticked — the form is
23
- * `noValidate`, so `required` alone would gate nothing. The statement is
24
- * wired by `aria-labelledby`, never a wrapping `<label>`, so clicking the
25
- * link follows it instead of toggling the box (the ConsentGroup rule).
26
- *
27
- * Composition (#6514): every control and message is a `@working-theory/ui`
28
- * primitive — `Label`/`Input`/`Textarea` for the fields, `Alert` for the
29
- * success and form-level error, `InlineMessage` for the per-field messages
30
- * (its `error` variant carries `role="alert"`), `Button` for submit. The honeypot hides behind
31
- * the `hidden` attribute on its wrapper rather than an inline style object.
32
- */
33
-
34
- import {
35
- Alert,
36
- AlertDescription,
37
- Button,
38
- Checkbox,
39
- type CheckboxProps,
40
- ConsentStatement,
41
- InlineMessage,
42
- Input,
43
- Label,
44
- localeHref,
45
- Textarea,
46
- } from "@working-theory/ui";
47
- import { useLocale, useTranslations } from "next-intl";
48
- import { useCallback, useState } from "react";
49
- import { useFormState, useFormStatus } from "react-dom";
50
-
51
- import { captureLeadAction } from "./contact.actions";
52
-
53
- export interface ContactFormInitialValues {
54
- name?: string;
55
- email?: string;
56
- message?: string;
57
- }
58
-
59
- type CheckedState = Parameters<NonNullable<CheckboxProps["onCheckedChange"]>>[0];
60
-
61
- interface ContactFormProps {
62
- initialValues?: ContactFormInitialValues;
63
- }
64
-
65
- function SubmitButton({ disabled }: { disabled: boolean }) {
66
- const t = useTranslations("contact");
67
- const { pending } = useFormStatus();
68
- return (
69
- <Button
70
- type="submit"
71
- disabled={pending || disabled}
72
- data-testid="contact-submit"
73
- >
74
- {pending ? t("sending") : t("sendMessage")}
75
- </Button>
76
- );
77
- }
78
-
79
- export function ContactForm({ initialValues }: ContactFormProps = {}) {
80
- const t = useTranslations("contact");
81
- const locale = useLocale();
82
- const [state, formAction] = useFormState(captureLeadAction, null);
83
- const [consented, setConsented] = useState(false);
84
- const handleConsentChange = useCallback((checked: CheckedState) => {
85
- setConsented(checked === true);
86
- }, []);
87
-
88
- if (state?.kind === "success") {
89
- return (
90
- <Alert variant="success" data-testid="contact-success">
91
- <AlertDescription>{t("successMessage")}</AlertDescription>
92
- </Alert>
93
- );
94
- }
95
-
96
- const fieldErrors = state?.kind === "error" ? (state.fieldErrors ?? {}) : {};
97
- const formError =
98
- state?.kind === "error" && !state.fieldErrors ? state.message : undefined;
99
-
100
- return (
101
- <form action={formAction} noValidate>
102
- {formError && (
103
- <Alert variant="error" data-testid="contact-form-error">
104
- <AlertDescription>{formError}</AlertDescription>
105
- </Alert>
106
- )}
107
-
108
- {/* Honeypot — hidden from users; bots fill it; API rejects non-empty */}
109
- <div hidden>
110
- <Input
111
- type="text"
112
- name="website"
113
- tabIndex={-1}
114
- aria-hidden="true"
115
- data-testid="contact-honeypot"
116
- />
117
- </div>
118
-
119
- <div>
120
- <Label htmlFor="contact-name">{t("nameLabel")}</Label>
121
- <Input
122
- id="contact-name"
123
- name="name"
124
- type="text"
125
- autoComplete="name"
126
- data-testid="contact-name-input"
127
- required
128
- defaultValue={initialValues?.name}
129
- aria-describedby={fieldErrors.name ? "contact-name-error" : undefined}
130
- />
131
- {fieldErrors.name && (
132
- <InlineMessage
133
- variant="error"
134
- id="contact-name-error"
135
- data-testid="contact-name-error"
136
- >
137
- {fieldErrors.name}
138
- </InlineMessage>
139
- )}
140
- </div>
141
-
142
- <div>
143
- <Label htmlFor="contact-email">{t("emailLabel")}</Label>
144
- <Input
145
- id="contact-email"
146
- name="email"
147
- type="email"
148
- autoComplete="email"
149
- data-testid="contact-email-input"
150
- required
151
- defaultValue={initialValues?.email}
152
- aria-describedby={
153
- fieldErrors.email ? "contact-email-error" : undefined
154
- }
155
- />
156
- {fieldErrors.email && (
157
- <InlineMessage
158
- variant="error"
159
- id="contact-email-error"
160
- data-testid="contact-email-error"
161
- >
162
- {fieldErrors.email}
163
- </InlineMessage>
164
- )}
165
- </div>
166
-
167
- <div>
168
- <Label htmlFor="contact-message">{t("messageLabel")}</Label>
169
- <Textarea
170
- id="contact-message"
171
- name="message"
172
- rows={5}
173
- data-testid="contact-message-input"
174
- defaultValue={initialValues?.message}
175
- aria-describedby={
176
- fieldErrors.message ? "contact-message-error" : undefined
177
- }
178
- />
179
- {fieldErrors.message && (
180
- <InlineMessage
181
- variant="error"
182
- id="contact-message-error"
183
- data-testid="contact-message-error"
184
- >
185
- {fieldErrors.message}
186
- </InlineMessage>
187
- )}
188
- </div>
189
-
190
- <div className="flex items-start gap-3">
191
- <Checkbox
192
- id="contact-consent"
193
- name="consent"
194
- value="true"
195
- checked={consented}
196
- onCheckedChange={handleConsentChange}
197
- aria-labelledby="contact-consent-statement"
198
- className="mt-0.5"
199
- data-testid="contact-consent"
200
- />
201
- <ConsentStatement
202
- id="contact-consent-statement"
203
- statement={t("consentStatement")}
204
- policies={{ terms: localeHref(locale, "/terms") }}
205
- />
206
- </div>
207
-
208
- <SubmitButton disabled={!consented} />
209
- </form>
210
- );
211
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * Tests for ContactForm's consent row (#6466, spec AC-2 / AC-3).
3
- *
4
- * ContactForm is the only form a /contact visitor can submit — the fallback
5
- * `ConversationalIntakeWrapper` swaps in on Skip or a failed auto-submit. Its
6
- * consent line must be a real control: a visible, unticked checkbox named by
7
- * a statement whose "Terms" is a link, and a submit button that stays
8
- * disabled until the box is ticked (the form is `noValidate`, so `required`
9
- * alone would gate nothing).
10
- *
11
- * jsdom lacks `useFormState`/`useFormStatus` (see
12
- * page-renderer-migration.test.tsx), so `react-dom` is extended with inert
13
- * stand-ins; the outermost network boundary (`@working-theory/api-client`)
14
- * is mocked so the real server action module loads without a network.
15
- */
16
- import { fireEvent, render, screen } from "@testing-library/react";
17
- import { localeHref } from "@working-theory/ui";
18
-
19
- const MESSAGES: Record<string, string> = {
20
- consentStatement: "I accept the [Terms](terms).",
21
- };
22
-
23
- jest.mock("next-intl", () => ({
24
- useTranslations: () => (key: string) => MESSAGES[key] ?? key,
25
- useLocale: () => "en",
26
- }));
27
-
28
- jest.mock("react-dom", () => ({
29
- ...jest.requireActual("react-dom"),
30
- useFormState: (_action: unknown, initial: unknown) => [initial, jest.fn()],
31
- useFormStatus: () => ({ pending: false }),
32
- }));
33
-
34
- jest.mock("@working-theory/api-client", () => ({
35
- leads: { submit: jest.fn() },
36
- }));
37
-
38
- import { ContactForm } from "../ContactForm.js";
39
-
40
- // jsdom does not implement ResizeObserver — the Radix Checkbox renders a
41
- // hidden form-bubble input that observes size. Polyfill with a no-op.
42
- beforeAll(() => {
43
- global.ResizeObserver = class ResizeObserver {
44
- observe() {}
45
- unobserve() {}
46
- disconnect() {}
47
- };
48
- });
49
-
50
- describe("ContactForm consent row (#6466)", () => {
51
- it("renders a visible, unticked checkbox (AC-2 i)", () => {
52
- render(<ContactForm />);
53
- const checkbox = screen.getByRole("checkbox");
54
- expect(checkbox).toBeVisible();
55
- expect(checkbox).toHaveAttribute("aria-checked", "false");
56
- });
57
-
58
- it("links 'Terms' to the locale-aware /terms route (AC-2 ii)", () => {
59
- render(<ContactForm />);
60
- const link = screen.getByRole("link", { name: "Terms" });
61
- expect(link).toHaveAttribute("href", localeHref("en", "/terms"));
62
- });
63
-
64
- it("names the checkbox by the consent statement (AC-2 iii)", () => {
65
- render(<ContactForm />);
66
- // jsdom's accname pads the inline link with a space ("Terms .").
67
- expect(screen.getByRole("checkbox")).toHaveAccessibleName(
68
- /^I accept the Terms ?\.$/,
69
- );
70
- });
71
-
72
- it("clicking the Terms link does not tick the box (AC-2 iv)", () => {
73
- render(<ContactForm />);
74
- const link = screen.getByRole("link", { name: "Terms" });
75
- // A wrapping <label> is what would forward the click to the box.
76
- expect(link.closest("label")).toBeNull();
77
- link.addEventListener("click", (event) => event.preventDefault());
78
- fireEvent.click(link);
79
- expect(screen.getByRole("checkbox")).toHaveAttribute(
80
- "aria-checked",
81
- "false",
82
- );
83
- });
84
-
85
- it("keeps submit disabled until the box is ticked (AC-3)", () => {
86
- render(<ContactForm />);
87
- const submit = screen.getByTestId("contact-submit");
88
- expect(submit).toBeDisabled();
89
- fireEvent.click(screen.getByRole("checkbox"));
90
- expect(screen.getByRole("checkbox")).toHaveAttribute(
91
- "aria-checked",
92
- "true",
93
- );
94
- expect(submit).toBeEnabled();
95
- });
96
- });
@@ -1,175 +0,0 @@
1
- ---
2
- type: "SoftwareProduct"
3
- slug: "product"
4
- title: "{Your product name}"
5
- description: "{One sentence on what your product does and who it is for}"
6
- placeholder: true
7
- sections:
8
- [
9
- {
10
- "id": "hero",
11
- "type": "hero",
12
- "data":
13
- {
14
- "eyebrow": "Product",
15
- "headline": "{Your product name}",
16
- "subheadline": "{One sentence on what your product does and who it is for}",
17
- "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
18
- "secondaryCta":
19
- {
20
- "label": "{Secondary CTA}",
21
- "href": "/pricing",
22
- "variant": "outline",
23
- },
24
- },
25
- },
26
- {
27
- "id": "core-features",
28
- "type": "feature",
29
- "data":
30
- {
31
- "variant": "default",
32
- "itemStyle": "plain",
33
- "eyebrow": "{Section label}",
34
- "headline": "{Section heading: the outcome this section promises}",
35
- "description": "{Two sentences on what these features let your customers do}",
36
- "features":
37
- [
38
- {
39
- "title": "{Feature name}",
40
- "description": "{Explain what this feature lets your customers do, in plain English with no jargon}",
41
- "icon": "rocket",
42
- "link":
43
- {
44
- "label": "Learn More",
45
- "href": "#how-it-works",
46
- "variant": "link",
47
- },
48
- },
49
- {
50
- "title": "{Feature name}",
51
- "description": "{Explain what this feature lets your customers do, in plain English with no jargon}",
52
- "icon": "zap",
53
- "link":
54
- {
55
- "label": "Learn More",
56
- "href": "#how-it-works",
57
- "variant": "link",
58
- },
59
- },
60
- {
61
- "title": "{Feature name}",
62
- "description": "{Explain what this feature lets your customers do, in plain English with no jargon}",
63
- "icon": "sparkles",
64
- "link":
65
- {
66
- "label": "Learn More",
67
- "href": "#how-it-works",
68
- "variant": "link",
69
- },
70
- },
71
- {
72
- "title": "{Feature name}",
73
- "description": "{Explain what this feature lets your customers do, in plain English with no jargon}",
74
- "icon": "gauge",
75
- "link":
76
- {
77
- "label": "Learn More",
78
- "href": "#how-it-works",
79
- "variant": "link",
80
- },
81
- },
82
- {
83
- "title": "{Feature name}",
84
- "description": "{Explain what this feature lets your customers do, in plain English with no jargon}",
85
- "icon": "shield-check",
86
- "link":
87
- {
88
- "label": "Learn More",
89
- "href": "#how-it-works",
90
- "variant": "link",
91
- },
92
- },
93
- ],
94
- },
95
- },
96
- {
97
- "id": "how-it-works",
98
- "type": "feature",
99
- "data":
100
- {
101
- "variant": "steps",
102
- "numbered": true,
103
- "eyebrow": "How it works",
104
- "headline": "{Get set up in number easy steps}",
105
- "description": "{Summarize the setup process briefly, so it reads as approachable and quick}",
106
- "features":
107
- [
108
- {
109
- "title": "{The first thing they do to get the outcome they want}",
110
- "description": "{Two sentences on what the customer does in this step and what they get}",
111
- },
112
- {
113
- "title": "{The second thing they do to get the outcome they want}",
114
- "description": "{Two sentences on what the customer does in this step and what they get}",
115
- },
116
- {
117
- "title": "{The third thing they do to get the outcome they want}",
118
- "description": "{Two sentences on what the customer does in this step and what they get}",
119
- },
120
- ],
121
- },
122
- },
123
- {
124
- "id": "integrations",
125
- "type": "cta",
126
- "data":
127
- {
128
- "variant": "minimal",
129
- "eyebrow": "Integrations",
130
- "headline": "Direct integrations with your favorite tools",
131
- "description": "{Describe what these integrations help your customers do, with one impressive fact about how they work}",
132
- "primaryCta":
133
- {
134
- "label": "Get Started",
135
- "href": "/contact",
136
- "variant": "outline",
137
- },
138
- "secondaryCta":
139
- {
140
- "label": "See all Integrations",
141
- "href": "#integrations-media",
142
- "variant": "link",
143
- },
144
- },
145
- },
146
- {
147
- "id": "integrations-media",
148
- "type": "media-band",
149
- "data": { "imagerySlot": true },
150
- },
151
- {
152
- "id": "switch",
153
- "type": "cta",
154
- "data":
155
- {
156
- "variant": "box",
157
- "headline": "{Switch to your product in time amount or less}",
158
- "description": "{You shouldn't have to experience a painful problem. With your product, you can finally achieve a value outcome or get a big improvement.}",
159
- "checklist":
160
- ["No credit card required", "Cancel any time", "Live chat support"],
161
- "imagerySlot": true,
162
- "primaryCta": { "label": "{Primary CTA}", "href": "/contact" },
163
- "secondaryCta":
164
- {
165
- "label": "{Secondary CTA}",
166
- "href": "/pricing",
167
- "variant": "outline",
168
- },
169
- "closingLine": "{Social Proof goes here}",
170
- },
171
- },
172
- ]
173
- ---
174
-
175
- {/* Your product's own page, served at /software/product. Its JSON-LD is a WebPage whose main entity is a SoftwareApplication named by this page's title, and the feature titles below become the site's featureList. Placeholder copy — replace every {brace} with your own words before you launch. */}