@usgb/forms 0.1.1 → 1.0.1

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 +263 -71
  2. package/dist/forms-manifest.json +251 -0
  3. package/dist/index.js +1032 -31
  4. package/dist/src/contract/collectContext.d.ts +9 -0
  5. package/dist/src/contract/collectContext.d.ts.map +1 -0
  6. package/dist/src/contract/createStubAdapter.d.ts +9 -0
  7. package/dist/src/contract/createStubAdapter.d.ts.map +1 -0
  8. package/dist/src/contract/types.d.ts +90 -0
  9. package/dist/src/contract/types.d.ts.map +1 -0
  10. package/dist/src/definitions/consents.d.ts +37 -0
  11. package/dist/src/definitions/consents.d.ts.map +1 -0
  12. package/dist/src/definitions/fields.d.ts +19 -0
  13. package/dist/src/definitions/fields.d.ts.map +1 -0
  14. package/dist/src/definitions/index.d.ts +7 -0
  15. package/dist/src/definitions/index.d.ts.map +1 -0
  16. package/dist/src/definitions/manifest.d.ts +8 -0
  17. package/dist/src/definitions/manifest.d.ts.map +1 -0
  18. package/dist/src/definitions/types.d.ts +82 -0
  19. package/dist/src/definitions/types.d.ts.map +1 -0
  20. package/dist/src/form/FormShell.d.ts +10 -0
  21. package/dist/src/form/FormShell.d.ts.map +1 -0
  22. package/dist/src/form/RegisteredTextField.d.ts +14 -0
  23. package/dist/src/form/RegisteredTextField.d.ts.map +1 -0
  24. package/dist/src/form/UsgbForm.d.ts +25 -0
  25. package/dist/src/form/UsgbForm.d.ts.map +1 -0
  26. package/dist/src/form/chunkFields.d.ts +17 -0
  27. package/dist/src/form/chunkFields.d.ts.map +1 -0
  28. package/dist/src/form/kinds/LeadKindForm.d.ts +4 -0
  29. package/dist/src/form/kinds/LeadKindForm.d.ts.map +1 -0
  30. package/dist/src/form/kinds/NewsletterKindForm.d.ts +4 -0
  31. package/dist/src/form/kinds/NewsletterKindForm.d.ts.map +1 -0
  32. package/dist/src/form/kinds/index.d.ts +6 -0
  33. package/dist/src/form/kinds/index.d.ts.map +1 -0
  34. package/dist/src/form/kinds/types.d.ts +11 -0
  35. package/dist/src/form/kinds/types.d.ts.map +1 -0
  36. package/dist/src/form/useUsgbFormSession.d.ts +41 -0
  37. package/dist/src/form/useUsgbFormSession.d.ts.map +1 -0
  38. package/dist/src/index.d.ts +14 -0
  39. package/dist/src/index.d.ts.map +1 -0
  40. package/dist/src/ui/Button.d.ts +7 -0
  41. package/dist/src/ui/Button.d.ts.map +1 -0
  42. package/dist/src/ui/ConsentField.d.ts +9 -0
  43. package/dist/src/ui/ConsentField.d.ts.map +1 -0
  44. package/dist/src/ui/FieldError.d.ts +8 -0
  45. package/dist/src/ui/FieldError.d.ts.map +1 -0
  46. package/dist/src/ui/FieldLabel.d.ts +9 -0
  47. package/dist/src/ui/FieldLabel.d.ts.map +1 -0
  48. package/dist/src/ui/TextField.d.ts +19 -0
  49. package/dist/src/ui/TextField.d.ts.map +1 -0
  50. package/dist/src/ui/icons.d.ts +4 -0
  51. package/dist/src/ui/icons.d.ts.map +1 -0
  52. package/dist/usgb-forms.1.0.1.js +44 -0
  53. package/dist/usgb-forms.css +596 -0
  54. package/dist/usgb-forms.js +44 -0
  55. package/forms-manifest.json +251 -0
  56. package/package.json +63 -58
  57. package/dist/index.cjs +0 -64
  58. package/dist/index.cjs.map +0 -1
  59. package/dist/index.d.cts +0 -15
  60. package/dist/index.d.ts +0 -15
  61. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -1,112 +1,304 @@
1
1
  # @usgb/forms
2
2
 
3
- Sample React package that builds with [tsup](https://tsup.egoist.dev/) and publishes to npm using [OIDC trusted publishing](https://docs.npmjs.com/trusted-publishers/). Releases do not use a long-lived `NPM_TOKEN`. Replace this code and this readme with real versions.
3
+ Shared lead and newsletter forms for the **PWA** (React) and **WordPress** (Web Component) hosts.
4
4
 
5
- ## What's included
5
+ You pick a predefined `formId`. The package owns fields, validation, consent copy, tracking keys, and layout. You own the submit endpoint, legal-document URLs, heading/subheading/CTA copy, and thank-you behavior.
6
6
 
7
- - React 18+ form primitives (`Form`, `TextField`) with TypeScript types
8
- - Dual ESM/CJS build output in `dist/`
9
- - CI on pull requests
10
- - Publish to npm on every push to `main` via OIDC (no git tags)
7
+ ## What you get
11
8
 
12
- ## Local development
9
+ | Artifact | Path / export | Who uses it |
10
+ | ------------- | --------------------------------------------------------------- | ----------------------------------- |
11
+ | React package | `@usgb/forms` → `dist/index.js` | PWA / any React host |
12
+ | Stylesheet | `@usgb/forms/usgb-forms.css` | **Required** for React hosts |
13
+ | Web Component | `dist/usgb-forms.js` (also versioned `usgb-forms.<version>.js`) | WordPress and other non-React hosts |
14
+ | Catalog | `@usgb/forms/manifest` → `forms-manifest.json` | CMS pickers, docs, tooling |
15
+
16
+ The Web Component script injects the same CSS once on load. React hosts must import the stylesheet themselves.
17
+
18
+ ## Mental model
19
+
20
+ | You pass | Package already decided |
21
+ | -------------------------------- | ------------------------------------------------------------------------------------- |
22
+ | `formId` | Fields, required/optional rules, consent bundle, tracking set, layout family (`kind`) |
23
+ | `adapter.submit` | Where the payload goes (never an attribute or hardcoded URL) |
24
+ | `legalUrls` / `*-url` | Hrefs for package-owned consent copy |
25
+ | `heading`, `subheading`, `ctaLabel` | Marketing copy around the form |
26
+ | `successMessage` or `successUrl` | Thank-you UX after accept |
27
+
28
+ Hosts **must not** invent field lists, consent wording, or tracking keys. New forms are new catalog entries in this package, not host-built schemas.
29
+
30
+ Use `listFormDefinitions()` (or the manifest) when a CMS needs a picker of available `formId`s. Deeper catalog rules: [`src/definitions/context.md`](src/definitions/context.md).
31
+
32
+ ## Available forms
33
+
34
+ | `formId` | Kind | Fields | Consent | Variants |
35
+ | -------------------- | ------------ | --------------------------------------------------------- | ------------------------------ | ------------------------------------------------------- |
36
+ | `main-investors-kit` | `lead` | First name, last name, email (required); phone (optional) | TCPA checkbox (`lead-tcpa` v1) | `dense`, `large`, `sidebar`, plus optional `two-column` |
37
+ | `newsletter` | `newsletter` | Email only | None | None — omit `variants` / `variant` |
38
+
39
+ Appearances for both: `on-light` | `on-dark`. Appearance only recolors the form for readability on a light or dark host background — it never sets a background, padding, or radius. The host container owns those.
40
+
41
+ `kind` is the layout family (lead grid vs inline newsletter row). The root exposes `data-kind` for CSS. Several catalog forms can share one kind (e.g. another lead kit that reuses the lead layout).
42
+
43
+ ## Install
44
+
45
+ Registry package name, scope, and CDN base URL are **TBD** — examples below use the working package name `@usgb/forms` and a placeholder CDN host. Replace them when publishing is finalized.
46
+
47
+ ### npm (React / PWA)
13
48
 
14
49
  ```bash
15
- npm install
16
- npm test
17
- npm run build
50
+ yarn add @usgb/forms
51
+ # or
52
+ npm install @usgb/forms
18
53
  ```
19
54
 
20
- Peer dependencies: `react` and `react-dom` >= 18.
55
+ Then import the component and stylesheet:
56
+
57
+ ```ts
58
+ import { UsgbForm } from '@usgb/forms'
59
+ import '@usgb/forms/usgb-forms.css'
60
+ ```
21
61
 
22
- ## Publish with OIDC
62
+ Peer dependency: `react` and `react-dom` `^18` or `^19`.
23
63
 
24
- Trusted publishing creates a trust relationship between npm and this repository. GitHub Actions mints a short-lived OIDC token; npm CLI 11.5.1+ exchanges it for a publish credential. No npm write token is stored in GitHub.
64
+ ### CDN (Web Component / WordPress)
25
65
 
26
- Requirements:
66
+ Load the built IIFE (styles inject themselves):
67
+
68
+ ```html
69
+ <script src="https://cdn.example.com/usgb-forms/usgb-forms.1.0.0.js"></script>
70
+ ```
71
+
72
+ Exact CDN hostname, path, and versioning scheme are **TBD**. Prefer a versioned filename (`usgb-forms.<version>.js`) so hosts can pin releases.
73
+
74
+ Until the package is published, you can still develop against a local checkout (`yarn add file:../usgb-forms` or `yarn link`).
75
+
76
+ ## React (PWA)
77
+
78
+ ```tsx
79
+ import { UsgbForm, createStubAdapter, PWA_LEGAL_URLS } from '@usgb/forms'
80
+ import '@usgb/forms/usgb-forms.css'
81
+ ;<UsgbForm
82
+ formId="main-investors-kit"
83
+ appearance="on-dark"
84
+ variants={['large', 'two-column']}
85
+ heading="Get My Free Guide"
86
+ subheading="Enter your details to receive the Main Investors Kit."
87
+ ctaLabel="GET MY FREE GUIDE"
88
+ source="cms-home"
89
+ campaign="spring-kit"
90
+ legalUrls={PWA_LEGAL_URLS}
91
+ successMessage="Thanks — we will send your kit shortly."
92
+ adapter={yourHostAdapter}
93
+ onAccepted={(payload) => {
94
+ /* analytics bridge */
95
+ }}
96
+ onFailed={(payload, message) => {
97
+ /* error bridge */
98
+ }}
99
+ />
100
+ ```
27
101
 
28
- - Node 22.14.0+ (this workflow uses Node 24)
29
- - npm 11.5.1+
30
- - GitHub-hosted runners (self-hosted runners are not supported)
31
- - A public GitHub repository if you want automatic provenance attestations
102
+ Newsletter (no consent, no variants):
32
103
 
33
- ### 1. Point `package.json` at the real package and repo
104
+ ```tsx
105
+ <UsgbForm
106
+ formId="newsletter"
107
+ appearance="on-light"
108
+ heading="Subscribe"
109
+ ctaLabel="SUBSCRIBE"
110
+ adapter={yourHostAdapter}
111
+ />
112
+ ```
34
113
 
35
- Update these fields before the first publish. npm checks that `repository.url` matches the GitHub repository that is publishing:
114
+ ### React props
115
+
116
+ | Prop | Required | Role |
117
+ | ------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- |
118
+ | `formId` | yes | Catalog key (`main-investors-kit`, `newsletter`, …) |
119
+ | `adapter` | yes | `HostAdapter` with `host`, `getTrackingHints()`, `submit()` |
120
+ | `appearance` | no | `on-light` (default) or `on-dark` |
121
+ | `variants` | no | Lead only. Default `['dense']`. One of `dense` \| `large` \| `sidebar`, optionally add `two-column` |
122
+ | `heading` / `subheading` | no | Title and supporting copy |
123
+ | `ctaLabel` | no | Submit button label. Falls back to the catalog `ctaLabel` for that `formId` |
124
+ | `source` / `campaign` | no | Placement / campaign context in the payload |
125
+ | `legalUrls` | when consent requires them | Host CMS paths for legal links in consent copy |
126
+ | `successMessage` | no | Inline thank-you when `successUrl` is omitted |
127
+ | `successUrl` | no | Redirect after accept (analytics / `onAccepted` are invoked first; see Success behavior) |
128
+ | `analytics` | no | `(event) => void` for view / validation / submit / success / failure |
129
+ | `className` | no | Extra class on `.usgb-form` |
130
+ | `onAccepted` / `onFailed` | no | Callbacks with the submission payload |
131
+
132
+ ## Web Component (WordPress)
133
+
134
+ Load one script. Styles are injected automatically.
135
+
136
+ ````html
137
+ <script src="https://cdn.example.com/usgb-forms/usgb-forms.1.0.0.js"></script>
138
+ <usgb-form
139
+ ```
140
+ form-id="main-investors-kit"
141
+ appearance="on-dark"
142
+ variant="large two-column"
143
+ heading="Get My Free Guide"
144
+ subheading="Enter your details to receive the Main Investors Kit."
145
+ cta="GET MY FREE GUIDE"
146
+ source="cms-home"
147
+ client-agreement-url="/client-agreement/"
148
+ privacy-policy-url="/privacy-policy/"
149
+ success-message="Thanks — we will send your kit shortly."
150
+ ></usgb-form>
151
+ ````
152
+
153
+ Submit is **not** an attribute. Markup alone uses a stub adapter. Assign a real `HostAdapter` in script:
154
+
155
+ ```html
156
+ <script>
157
+ const el = document.querySelector('usgb-form')
158
+ el.adapter = {
159
+ host: 'wordpress',
160
+ getTrackingHints() {
161
+ // Cookies, UTMs, click ids — keys must match the package tracking registry
162
+ return {}
163
+ },
164
+ async submit(payload) {
165
+ const res = await fetch('/your-form-submit', {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify(payload)
169
+ })
170
+ if (!res.ok) return { status: 'failed', message: res.statusText }
171
+ return { status: 'accepted' }
172
+ }
173
+ }
174
+
175
+ el.addEventListener('usgb-form:success', (e) => {
176
+ /* e.detail is SubmissionPayload */
177
+ })
178
+ el.addEventListener('usgb-form:failure', (e) => {
179
+ /* e.detail = { payload, message } */
180
+ })
181
+ </script>
182
+ ```
36
183
 
37
- ```json
38
- {
39
- "name": "@your-scope/forms",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/OWNER/REPO.git"
43
- }
184
+ ### Attribute map
185
+
186
+ | Attribute | React prop |
187
+ | ----------------------------------------------- | ------------------------------------------------------------- |
188
+ | `form-id` | `formId` |
189
+ | `appearance` | `appearance` |
190
+ | `variant` | `variants` (space-separated) |
191
+ | `heading` / `subheading` | `heading` / `subheading` |
192
+ | `cta` | `ctaLabel` |
193
+ | `source` / `campaign` | `source` / `campaign` |
194
+ | `success-message` / `success-url` | `successMessage` / `successUrl` |
195
+ | `client-agreement-url`, `privacy-policy-url`, … | `legalUrls.*` |
196
+ | `host` | Used only for the stub adapter before you assign `el.adapter` |
197
+
198
+ ## Host adapter
199
+
200
+ ```ts
201
+ interface HostAdapter {
202
+ host: 'pwa' | 'wordpress'
203
+ getTrackingHints(): Partial<Record<TrackingKey, string | undefined>>
204
+ submit(payload: SubmissionPayload): Promise<SubmitResult>
44
205
  }
45
206
  ```
46
207
 
47
- ### 2. Create the npm package (first publish only)
208
+ - `submit` must return `{ status: 'accepted' }` or `{ status: 'failed', message?: string }`.
209
+ - The package never embeds Workato, Magento, or WordPress endpoints.
210
+ - `createStubAdapter('pwa' | 'wordpress')` accepts every payload (playground / markup-only WC).
211
+ - Tracking keys and destination names live in `forms-manifest.json`. Hosts may supply values for registered keys; they must not invent new ones.
48
212
 
49
- Trusted publishers are configured on an existing package. Create the package on [npmjs.com](https://www.npmjs.com/) if it does not exist yet, or publish once with a granular token and then switch to OIDC.
213
+ ### Payload shape (summary)
50
214
 
51
- ### 3. Add a trusted publisher on npmjs.com
215
+ Successful submits send a nested envelope: `formId`, `definitionVersion`, `submissionContractVersion`, `idempotencyKey`, `data` (field values), optional `consent`, and `context` (`host`, `source`, `campaign`, `page`, `tracking`). Exact field and tracking sets come from the form’s catalog entry.
52
216
 
53
- Open the package **Settings → Trusted Publisher**, choose **GitHub Actions**, and enter values that match this repository exactly (all fields are case-sensitive):
217
+ ## Consent and legal URLs
54
218
 
55
- | Field | Value |
56
- | --- | --- |
57
- | Organization or user | `OWNER` |
58
- | Repository | `REPO` |
59
- | Workflow filename | `publish.yml` |
60
- | Environment name | `npm-publish` |
61
- | Allowed actions | `npm publish` |
219
+ Consent bundles are versioned and package-owned (checkboxes and/or clickwrap notices). Hosts only supply document **hrefs**.
62
220
 
63
- Use only the workflow filename (`publish.yml`), not `.github/workflows/publish.yml`. npm validates the top-level workflow that started the run.
221
+ - `main-investors-kit` uses `lead-tcpa` v1 and requires `clientAgreement` and `privacyPolicy`.
222
+ - PWA Magento defaults: `PWA_LEGAL_URLS` → `/content/client-agreement`, `/content/privacy-policy`.
223
+ - WordPress must pass every URL required by the selected consent. A consent form with a missing required URL **does not render**.
224
+ - Checkbox values land in `consent.values` (and mirrored in `data`). Notices have no boolean field; the payload still records consent definition id/version.
225
+ - Distinct legal wording requires a new consent id/version in this package — not freeform copy from the host.
64
226
 
65
- ### 4. Protect the GitHub environment (recommended)
227
+ `LEAD_CLICKWRAP_V1` (“By clicking the button…”) is in the catalog and needs five legal URLs (`privacyPolicy`, `userAgreement`, `marketLossPolicy`, `electronicDisclaimer`, `termsOfSale`). It is not attached to a `formId` until that recipe is chosen.
66
228
 
67
- Create a GitHub environment named `npm-publish` and add required reviewers if you want a human approval step before each release. The environment name in the workflow and on npmjs.com must match.
229
+ ## Success behavior
68
230
 
69
- ### 5. Release
231
+ 1. Adapter returns `accepted`.
232
+ 2. `analytics({ type: 'success' })` and `onAccepted` / `usgb-form:success` run synchronously. The package does not `await` them.
233
+ 3. If `successUrl` / `success-url` is set, the page calls `window.location.assign` immediately after those callbacks return.
234
+ 4. Otherwise the form shows `successMessage` / `success-message`, or a package default thank-you line.
70
235
 
71
- Bump `version` in `package.json` without creating a git tag, then push to `main`. That push runs [`.github/workflows/publish.yml`](.github/workflows/publish.yml).
236
+ ### Redirect vs analytics
72
237
 
73
- ```bash
74
- npm version patch --no-git-tag-version # or minor / major
75
- git add package.json package-lock.json
76
- git commit -m "Release 0.1.1"
77
- git push origin main
238
+ Invocation order is fixed (hooks first, then navigate). Delivery is not. `location.assign` starts unload in the same turn, so a typical GTM / `gtag` / `dataLayer.push` / image pixel started in `analytics` or `onAccepted` can be cancelled before the beacon leaves — especially on Safari and slow networks. Same for async work inside a `usgb-form:success` listener: the CustomEvent is dispatched synchronously, but anything it schedules after return can die with the document.
239
+
240
+ This package does **not** wait on a returned Promise, add a flush timeout, or call `sendBeacon` on the host’s behalf. A hung analytics callback must not trap the user on the submit screen.
241
+
242
+ What is reliable today:
243
+
244
+ - Inline thank-you (`successMessage` only) — no navigation, so tags usually complete.
245
+ - Same-document hash redirects (`#thank-you`) — no unload.
246
+ - Host `analytics` that uses `navigator.sendBeacon` or `fetch(..., { keepalive: true })`.
247
+ - Conversion measured on the thank-you page itself, not on this `success` event.
248
+
249
+ `submit_attempt` is safer than `success` because it fires before `adapter.submit` and has the round-trip to flush. Prefer the thank-you page (or a beacon) when `successUrl` is a full navigation.
250
+
251
+ ## Styling
252
+
253
+ Light DOM with stable `usgb-` class names (not CSS-in-JS). Isolation comes from the prefix and from styling `.usgb-field-control` / `.usgb-btn` instead of bare `input` / `button`. Host theme rules can still win; load host CSS **after** this package’s stylesheet and override under `.usgb-form`.
254
+
255
+ | Host | How styles load |
256
+ | ------------- | ---------------------------------------------- |
257
+ | React | `import '@usgb/forms/usgb-forms.css'` |
258
+ | Web Component | Injected as `<style id="usgb-forms-css">` once |
259
+
260
+ Prefer **custom properties** on `.usgb-form` for color, type, and spacing:
261
+
262
+ ```css
263
+ .usgb-form {
264
+ --usgb-button-background: #123456;
265
+ --usgb-field-border: #cccccc;
266
+ }
267
+
268
+ .usgb-form[data-appearance='on-dark'] {
269
+ --usgb-button-background: #f4e08e;
270
+ }
78
271
  ```
79
272
 
80
- The workflow:
273
+ Use class selectors when a token does not exist (`.usgb-form .usgb-btn { border-radius: 0; }`).
81
274
 
82
- 1. Requests `id-token: write` so Actions can mint an OIDC token
83
- 2. Installs Node 24 (ships npm 11.x)
84
- 3. Runs tests and the library build
85
- 4. Publishes the version in `package.json` with **no token secret**, or skips if that version is already on npm
275
+ Stable hooks: `.usgb-form` (`data-appearance`, `data-variant`, `data-kind`), `.usgb-form-heading`, `.usgb-form-subheading`, `.usgb-form-grid`, `.usgb-form-inline`, `.usgb-field-control`, `.usgb-field-error`, `.usgb-consent-*`, `.usgb-btn`, `.usgb-form-status-*`.
86
276
 
87
- Pushes to `main` that do not change the version still run the workflow; they skip `npm publish` so the job stays green.
277
+ Semantic tokens (`--usgb-field-*`, `--usgb-button-*`, `--usgb-newsletter-underline`, …) are the first place to retheme. Palette vars (`--usgb-blue-1`, …) are a PWA snapshot. Error and checkbox glyphs are inline SVGs that use `currentColor` — override `--usgb-alert-danger` or `--usgb-checkbox-mark` to recolor them.
88
278
 
89
- npm automatically attaches [provenance](https://docs.npmjs.com/generating-provenance-statements/) for public packages published from a public repository. Do not pass `--provenance`; the CLI enables it when OIDC publishing is in use.
279
+ Appearance never paints a card. Put the form on a dark host surface and pass `appearance="on-dark"` (or `on-light` on a light surface):
90
280
 
91
- ### 6. Lock down token publishing (recommended)
281
+ ```html
282
+ <div style="background: #001f3d; padding: 2rem; border-radius: 0.75rem">
283
+ <usgb-form form-id="main-investors-kit" appearance="on-dark" …></usgb-form>
284
+ </div>
285
+ ```
92
286
 
93
- After the first OIDC publish succeeds, on the package **Settings → Publishing access** select **Require two-factor authentication and disallow tokens**. Trusted publishing keeps working because it does not use classic npm tokens.
287
+ Visual styles are a ported snapshot of the PWA form kit. When PWA form styles change, update this package and bump the version.
94
288
 
95
- ## Workflow notes
289
+ ## Develop (package maintainers)
96
290
 
97
- - `permissions.id-token: write` is required. Without it the CLI cannot obtain an OIDC token and publish fails with `ENEEDAUTH`.
98
- - Do not set `NODE_AUTH_TOKEN` or `NPM_TOKEN` on the publish step. A leftover token can mask a broken trusted-publisher config.
99
- - `actions/setup-node` must set `registry-url: https://registry.npmjs.org`.
100
- - If this workflow is later called from another workflow, npm still checks the **calling** workflow filename, and both jobs need `id-token: write`.
101
- - Private dependencies still need a **read-only** npm token for `npm ci`. Trusted publishing only authenticates `npm publish`.
291
+ ```bash
292
+ yarn install
293
+ yarn dev # Vite playground (tries :5174)
294
+ yarn build
295
+ yarn typecheck
296
+ yarn lint
297
+ yarn format
298
+ ```
102
299
 
103
- ## Troubleshooting
300
+ Prettier and ESLint match magento-frontend (4-space indent, single quotes, no semicolons, print width 80).
104
301
 
105
- | Symptom | Likely cause |
106
- | --- | --- |
107
- | `ENEEDAUTH` / unable to authenticate | Workflow filename, repo, or environment does not match the trusted publisher |
108
- | 404 during OIDC exchange | Trusted publisher is missing, or npm CLI is older than 11.5.1 |
109
- | Provenance missing | Repository or package is private |
110
- | `npm ci` fails on private deps | Need a read-only install token; OIDC does not apply to install |
302
+ ## Git
111
303
 
112
- See the [npm trusted publishing docs](https://docs.npmjs.com/trusted-publishers/) for the full provider matrix and security recommendations.
304
+ Local repo only. Connect a GitHub remote later when ready to publish.
@@ -0,0 +1,251 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "package": {
4
+ "name": "@usgb/forms",
5
+ "compatiblePackageVersions": "^1.0.0"
6
+ },
7
+ "submissionContract": {
8
+ "version": "1.0.0",
9
+ "transport": "server-proxy",
10
+ "payloadShape": "data-and-context"
11
+ },
12
+ "trackingRegistry": {
13
+ "page_url": {
14
+ "destination": "form_submission_url",
15
+ "category": "page",
16
+ "collection": "server",
17
+ "required": true,
18
+ "states": ["collected", "unavailable"],
19
+ "description": "Canonical landing-page URL at submission time"
20
+ },
21
+ "page_title": {
22
+ "destination": "pageName",
23
+ "category": "page",
24
+ "collection": "server",
25
+ "required": false,
26
+ "states": ["collected", "unavailable"]
27
+ },
28
+ "referrer": {
29
+ "destination": "referrer",
30
+ "category": "page",
31
+ "collection": "browser",
32
+ "required": false,
33
+ "states": ["collected", "unavailable"]
34
+ },
35
+ "first_touch_url": {
36
+ "destination": "crossDomain_landing_url",
37
+ "category": "attribution",
38
+ "collection": "host-adapter",
39
+ "required": false,
40
+ "states": ["collected", "unavailable"]
41
+ },
42
+ "utm_source": {
43
+ "destination": "utm_source",
44
+ "category": "attribution",
45
+ "collection": "host-adapter",
46
+ "required": false,
47
+ "states": ["collected", "unavailable"]
48
+ },
49
+ "utm_medium": {
50
+ "destination": "utm_medium",
51
+ "category": "attribution",
52
+ "collection": "host-adapter",
53
+ "required": false,
54
+ "states": ["collected", "unavailable"]
55
+ },
56
+ "utm_campaign": {
57
+ "destination": "utm_campaign",
58
+ "category": "attribution",
59
+ "collection": "host-adapter",
60
+ "required": false,
61
+ "states": ["collected", "unavailable"]
62
+ },
63
+ "utm_term": {
64
+ "destination": "utm_term",
65
+ "category": "attribution",
66
+ "collection": "host-adapter",
67
+ "required": false,
68
+ "states": ["collected", "unavailable"]
69
+ },
70
+ "utm_content": {
71
+ "destination": "utm_content",
72
+ "category": "attribution",
73
+ "collection": "host-adapter",
74
+ "required": false,
75
+ "states": ["collected", "unavailable"]
76
+ },
77
+ "gclid": {
78
+ "destination": "gclid",
79
+ "category": "click-id",
80
+ "collection": "host-adapter",
81
+ "required": false,
82
+ "states": ["collected", "unavailable"]
83
+ },
84
+ "msclkid": {
85
+ "destination": "msclkid",
86
+ "category": "click-id",
87
+ "collection": "host-adapter",
88
+ "required": false,
89
+ "states": ["collected", "unavailable"]
90
+ },
91
+ "gbraid": {
92
+ "destination": "gbraid",
93
+ "category": "click-id",
94
+ "collection": "host-adapter",
95
+ "required": false,
96
+ "states": ["collected", "unavailable"]
97
+ },
98
+ "wbraid": {
99
+ "destination": "wbraid",
100
+ "category": "click-id",
101
+ "collection": "host-adapter",
102
+ "required": false,
103
+ "states": ["collected", "unavailable"]
104
+ },
105
+ "hubspot_utk": {
106
+ "destination": "hubspot_utk",
107
+ "category": "identity",
108
+ "collection": "host-adapter",
109
+ "required": false,
110
+ "states": ["collected", "unavailable", "consent-withheld"]
111
+ },
112
+ "ga_client_id": {
113
+ "destination": "ga_client_id",
114
+ "category": "analytics",
115
+ "collection": "host-adapter",
116
+ "required": false,
117
+ "states": ["collected", "unavailable", "consent-withheld"]
118
+ },
119
+ "ga_session_id": {
120
+ "destination": "session_id_start",
121
+ "category": "analytics",
122
+ "collection": "host-adapter",
123
+ "required": false,
124
+ "states": ["collected", "unavailable", "consent-withheld"]
125
+ },
126
+ "snowplow_id": {
127
+ "destination": "snowplow_id",
128
+ "category": "analytics",
129
+ "collection": "host-adapter",
130
+ "required": false,
131
+ "states": ["collected", "unavailable", "consent-withheld"]
132
+ },
133
+ "vwo_uuid": {
134
+ "destination": "vwo_uuid",
135
+ "category": "experiment",
136
+ "collection": "host-adapter",
137
+ "required": false,
138
+ "states": ["collected", "unavailable", "consent-withheld"]
139
+ },
140
+ "user_agent": {
141
+ "destination": "header_http_client_details",
142
+ "category": "technical",
143
+ "collection": "server",
144
+ "required": false,
145
+ "states": ["collected", "unavailable"]
146
+ },
147
+ "ip_address": {
148
+ "destination": "ipAddress",
149
+ "category": "technical",
150
+ "collection": "server",
151
+ "required": false,
152
+ "states": ["collected", "unavailable"]
153
+ }
154
+ },
155
+ "forms": {
156
+ "main-investors-kit": {
157
+ "definitionVersion": "1",
158
+ "kind": "lead",
159
+ "flow": { "type": "single" },
160
+ "supportedHosts": ["pwa", "wordpress"],
161
+ "fields": [
162
+ { "name": "firstname", "required": true, "group": "name" },
163
+ { "name": "lastname", "required": true, "group": "name" },
164
+ { "name": "email", "required": true },
165
+ { "name": "phone", "required": false }
166
+ ],
167
+ "trackingFields": [
168
+ "page_url",
169
+ "page_title",
170
+ "referrer",
171
+ "first_touch_url",
172
+ "utm_source",
173
+ "utm_medium",
174
+ "utm_campaign",
175
+ "utm_term",
176
+ "utm_content",
177
+ "gclid",
178
+ "msclkid",
179
+ "gbraid",
180
+ "wbraid",
181
+ "hubspot_utk",
182
+ "ga_client_id",
183
+ "ga_session_id",
184
+ "snowplow_id",
185
+ "vwo_uuid",
186
+ "user_agent",
187
+ "ip_address"
188
+ ],
189
+ "ctaLabel": "GET MY FREE GUIDE",
190
+ "consent": {
191
+ "definitionId": "lead-tcpa",
192
+ "definitionVersion": "1",
193
+ "required": true,
194
+ "submitFields": [
195
+ "tcpa_form_consent_granted",
196
+ "tcpa_form_opt_in_date"
197
+ ]
198
+ },
199
+ "editor": {
200
+ "label": "Main Investors Kit",
201
+ "description": "Standard lead form: name, email, phone, TCPA consent. Use on full-width landing pages and PWA kit/sidebar placements.",
202
+ "whenToUse": "Default kit request. Do not use for affiliate landers or IRA-specific leads.",
203
+ "whenNotToUse": "Affiliate pages (separate definition). IRA kit (separate definition when added).",
204
+ "supportedAppearances": ["on-light", "on-dark"],
205
+ "supportedVariants": ["dense", "large", "sidebar", "two-column"]
206
+ }
207
+ },
208
+ "newsletter": {
209
+ "definitionVersion": "1",
210
+ "kind": "newsletter",
211
+ "flow": { "type": "single" },
212
+ "supportedHosts": ["pwa"],
213
+ "fields": [{ "name": "email", "required": true }],
214
+ "trackingFields": [
215
+ "page_url",
216
+ "page_title",
217
+ "referrer",
218
+ "utm_source",
219
+ "utm_medium",
220
+ "utm_campaign",
221
+ "utm_term",
222
+ "utm_content"
223
+ ],
224
+ "ctaLabel": "SUBSCRIBE",
225
+ "editor": {
226
+ "label": "Newsletter",
227
+ "description": "Email-only newsletter signup. No TCPA consent.",
228
+ "whenToUse": "Footer or landing newsletter capture where only an email is required.",
229
+ "whenNotToUse": "Lead kits that need name, phone, or TCPA consent.",
230
+ "supportedAppearances": ["on-light", "on-dark"],
231
+ "supportedVariants": []
232
+ }
233
+ }
234
+ },
235
+ "hostAdapterContract": {
236
+ "hosts": ["pwa", "wordpress"],
237
+ "mayProvide": [
238
+ "browser cookies",
239
+ "URL parameters",
240
+ "page and placement context",
241
+ "host/store identity"
242
+ ],
243
+ "mustNotDefine": [
244
+ "new tracking keys",
245
+ "destination property names",
246
+ "consent definitions",
247
+ "form fields",
248
+ "submission endpoints"
249
+ ]
250
+ }
251
+ }