@duffcloudservices/site-forms 0.1.1 → 0.1.2

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.
package/README.md CHANGED
@@ -1,260 +1,260 @@
1
- # @duffcloudservices/site-forms
2
-
3
- Shared `<DcsForm/>` runtime for DCS customer sites. Renders a managed
4
- form definition (created in the portal Form Manager) from a build-time
5
- `.dcs/forms/<formId>.yaml` snapshot, validates user input, and posts
6
- submissions to the public site-forms API.
7
-
8
- This is the single import surface for managed-form runtime code on
9
- customer sites — do not redefine field components per site.
10
-
11
- > **Inside the `dcs-again` workspace** the package is also reachable
12
- > as `@duffcloudservices/site-forms` via `workspace:*` (the workspace
13
- > name and the published name are the same).
14
- > **In sibling customer-site repos** install the published package
15
- > from public npm — see [`PUBLISHING.md`](./PUBLISHING.md) for the
16
- > consumption story and registry setup.
17
-
18
- ## Install
19
-
20
- ### In a workspace app (inside `dcs-again`)
21
-
22
- ```jsonc
23
- // portal/package.json
24
- {
25
- "dependencies": {
26
- "@duffcloudservices/site-forms": "workspace:*"
27
- }
28
- }
29
- ```
30
-
31
- Then `pnpm install` from the repo root.
32
-
33
- ### In a sibling customer-site repo (e.g. `ktbraunlaw`, `kept`)
34
-
35
- ```powershell
36
- pnpm add @duffcloudservices/site-forms
37
- ```
38
-
39
- See [`PUBLISHING.md`](./PUBLISHING.md) for the registry, version, and
40
- release-workflow details.
41
-
42
- ## Vite setup
43
-
44
- Three pieces are required in the consuming site:
45
-
46
- 1. **`vite-plugin-yaml`** so YAML modules return parsed objects:
47
-
48
- ```ts
49
- // vite.config.ts
50
- import yaml from '@modyfi/vite-plugin-yaml'
51
-
52
- export default defineConfig({
53
- plugins: [vue(), yaml()],
54
- })
55
- ```
56
-
57
- Without it, the loader falls back to parsing raw strings via
58
- `js-yaml`, which works but pays the parse cost at boot.
59
-
60
- 2. **A `formsModules` loader** in your site that does the
61
- `import.meta.glob` from a path Vite can resolve (see below).
62
-
63
- 3. **Env vars** the runtime reads:
64
-
65
- | Variable | Purpose |
66
- | ----------------------- | ---------------------------------------------------- |
67
- | `VITE_DCS_PUBLIC_API` | Base URL of the DCS public API (no trailing slash). |
68
- | `VITE_DCS_SITE_SLUG` | Default site slug used when the prop is omitted. |
69
-
70
- ### Why the `formsModules` prop is required in real sites
71
-
72
- `<DcsForm/>` ships with an internal `import.meta.glob('/.dcs/forms/*.yaml')`
73
- fallback, but Vite resolves the leading `/` against the **consumer's
74
- Vite project root** (the directory containing `vite.config.ts`).
75
- On every customer-site repo today, the `.dcs/forms/` directory lives
76
- at the **repo root**, one or more levels above the Vite root
77
- (typically `site/` or `docs/`). The internal glob therefore matches
78
- nothing and you get:
79
-
80
- ```
81
- [@duffcloudservices/site-forms] No form definition found for "contact".
82
- Expected a YAML at /.dcs/forms/contact.yaml.
83
- ```
84
-
85
- The fix is a one-file loader the rest of your site imports from.
86
-
87
- #### Vue SPA (`vite.config.ts` in `site/`)
88
-
89
- ```ts
90
- // site/src/dcs-forms.ts
91
- const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
92
- eager: true,
93
- import: 'default',
94
- })
95
- export const dcsFormsModules: Record<string, unknown> = modules
96
- ```
97
-
98
- #### VitePress (`vite` block in `docs/.vitepress/config.ts`)
99
-
100
- ```ts
101
- // docs/.vitepress/dcs-forms-loader.ts
102
- const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
103
- eager: true,
104
- import: 'default',
105
- })
106
- export const dcsFormsModules: Record<string, unknown> = modules
107
- ```
108
-
109
- The relative depth (`../../`) depends on where the loader file lives
110
- relative to the repo root. Adjust as needed.
111
-
112
- ## Usage
113
-
114
- Place a YAML file at `<site>/.dcs/forms/contact.yaml`:
115
-
116
- ```yaml
117
- formId: contact
118
- title: Contact Us
119
- submission:
120
- kind: lead
121
- fields:
122
- - id: name
123
- type: text
124
- label: Name
125
- required: true
126
- - id: email
127
- type: email
128
- label: Email
129
- required: true
130
- - id: message
131
- type: textarea
132
- label: Message
133
- required: true
134
- ```
135
-
136
- Then in any page component:
137
-
138
- ```vue
139
- <script setup lang="ts">
140
- import { DcsForm } from '@duffcloudservices/site-forms'
141
- import { dcsFormsModules } from '@/dcs-forms'
142
- </script>
143
-
144
- <template>
145
- <DcsForm
146
- form-id="contact"
147
- :forms-modules="dcsFormsModules"
148
- @submit-success="onSuccess"
149
- @submit-error="onError"
150
- />
151
- </template>
152
- ```
153
-
154
- ## Props
155
-
156
- | Prop | Type | Default | Notes |
157
- | -------------------- | --------------------------------- | -------------------------------------- | ----------------------------------------------------------- |
158
- | `formId` | `string` (required) | — | Matches `.dcs/forms/<formId>.yaml`. |
159
- | `siteSlug` | `string` | `import.meta.env.VITE_DCS_SITE_SLUG` | Path segment in the submission URL. |
160
- | `definitionOverride` | `PortalFormDefinition` | — | Used by the portal preview iframe to show in-flight edits. |
161
- | `apiBase` | `string` | `import.meta.env.VITE_DCS_PUBLIC_API` | Override for tests / non-prod environments. |
162
- | `captchaToken` | `string` | — | Attached to the submission payload when set. |
163
- | `formsModules` | `Record<string, unknown>` | internal fallback glob (rarely matches) | **Required in real sites.** Pass a `Record<string, unknown>` from your own `import.meta.glob('../../.dcs/forms/*.yaml', { eager: true, import: 'default' })` — see Vite setup section. |
164
-
165
- ## Emits
166
-
167
- | Event | Payload | When |
168
- | ------------------ | ---------------------- | ------------------------------------------- |
169
- | `submit-success` | `DcsFormSubmitSuccess` | API responded `2xx`. |
170
- | `submit-error` | `DcsFormSubmitError` | Network or non-2xx response after retries. |
171
- | `validation-error` | `FormErrors` | Submit attempted with invalid required/regex/etc. fields. |
172
-
173
- ## Slots
174
-
175
- Every slot exposes scoped data so consumers (KT Braun, Kept) can swap
176
- shadcn primitives in without forking field components.
177
-
178
- | Slot | Scope | Default |
179
- | ---------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
180
- | `header` | `{ definition }` | `<h2>` + description |
181
- | `progress` | `{ current, total, step }` | `Step N of M — Title` (multi-step only) |
182
- | `actions` | `{ isFirstStep, isLastStep, submitting, prev, next }` | Plain `<button>` elements |
183
- | `success` | `{ definition }` | `definition.successMessage` |
184
- | `missing` | `{ formId }` | Friendly fallback when the YAML can't be found |
185
-
186
- Per-field components (`DcsFormText` etc.) expose `#input` slots so a
187
- shadcn site can replace the underlying primitive while keeping the
188
- wrapper, label, help, and error-message structure.
189
-
190
- ## Composables
191
-
192
- For sites that want a fully custom layout, drop `<DcsForm/>` and use
193
- the underlying composables directly:
194
-
195
- ```ts
196
- import {
197
- useDcsForm,
198
- validateForm,
199
- submitFormValues,
200
- parseFormYaml,
201
- } from '@duffcloudservices/site-forms'
202
- ```
203
-
204
- - `useDcsForm({ definition })` — reactive `values`, `errors`, `steps`,
205
- `next`, `prev`, `validateAll`, `collectSubmissionValues`, etc.
206
- - `validateForm(def, values, fieldIds?)` — pure validator usable in
207
- any setting (server-side, tests, custom adapters).
208
- - `submitFormValues({ apiBase, siteSlug, payload })` — one-shot POST
209
- with a single retry on 5xx and `multipart/form-data` when files are
210
- present.
211
-
212
- ## Visual editor integration
213
-
214
- The form root carries `data-form-key="<formId>"` and every field
215
- wrapper carries `data-form-field-key="<fieldId>"`. The portal preview
216
- iframe bridge uses these to highlight and select form regions. Do not
217
- strip them in custom layouts.
218
-
219
- ## Schema validation
220
-
221
- In dev (`import.meta.env.DEV === true`) the runtime validates each
222
- loaded definition against the JSON Schema bundled in
223
- `src/schema/form-definition.schema.json` (snapshot of
224
- `contracts/dist/form-definition.schema.json`) and logs failures via
225
- `console.warn`. Production builds skip the warning to avoid noisy
226
- end-user consoles.
227
-
228
- When the contracts schema is regenerated (`pnpm --filter @dcs/contracts
229
- generate`), refresh the snapshot:
230
-
231
- ```powershell
232
- Copy-Item ../../contracts/dist/form-definition.schema.json ./src/schema/form-definition.schema.json -Force
233
- pnpm --filter @duffcloudservices/site-forms test --run
234
- ```
235
-
236
- ## Scripts
237
-
238
- ```powershell
239
- pnpm --filter @duffcloudservices/site-forms build # vite library build (esm + dts)
240
- pnpm --filter @duffcloudservices/site-forms test # vitest --run
241
- pnpm --filter @duffcloudservices/site-forms type-check # vue-tsc --noEmit
242
- ```
243
-
244
- ## Related docs
245
-
246
- - **Authoring guide** — [`.docs/forms/AUTHORING.md`](../../.docs/forms/AUTHORING.md)
247
- covers the YAML schema, worked examples, validation flow, HIPAA
248
- guardrails, and the hand-coded → `<DcsForm/>` migration recipe.
249
- - **Publishing** — [`PUBLISHING.md`](./PUBLISHING.md) covers the
250
- registry, OIDC trusted publishing, version bump policy, and the
251
- exact dep line sibling customer-site repos should add.
252
- - **Validation CLI** — [`cli/forms/README.md`](../../cli/forms/README.md)
253
- documents `dcs forms validate` and `dcs forms doctor`, which lint
254
- the `.dcs/forms/*.yaml` files in a customer-site repo.
255
-
256
- ## Ownership
257
-
258
- Per `packages/README.md`: external/consumer-facing docs live here, not
259
- in repo-root docs. Cross-cutting details (e.g. the public submissions
260
- API contract) belong in `contracts/README.md`.
1
+ # @duffcloudservices/site-forms
2
+
3
+ Shared `<DcsForm/>` runtime for DCS customer sites. Renders a managed
4
+ form definition (created in the portal Form Manager) from a build-time
5
+ `.dcs/forms/<formId>.yaml` snapshot, validates user input, and posts
6
+ submissions to the public site-forms API.
7
+
8
+ This is the single import surface for managed-form runtime code on
9
+ customer sites — do not redefine field components per site.
10
+
11
+ > **Inside the `dcs-again` workspace** the package is also reachable
12
+ > as `@duffcloudservices/site-forms` via `workspace:*` (the workspace
13
+ > name and the published name are the same).
14
+ > **In sibling customer-site repos** install the published package
15
+ > from public npm — see [`PUBLISHING.md`](./PUBLISHING.md) for the
16
+ > consumption story and registry setup.
17
+
18
+ ## Install
19
+
20
+ ### In a workspace app (inside `dcs-again`)
21
+
22
+ ```jsonc
23
+ // portal/package.json
24
+ {
25
+ "dependencies": {
26
+ "@duffcloudservices/site-forms": "workspace:*"
27
+ }
28
+ }
29
+ ```
30
+
31
+ Then `pnpm install` from the repo root.
32
+
33
+ ### In a sibling customer-site repo (e.g. `ktbraunlaw`, `kept`)
34
+
35
+ ```powershell
36
+ pnpm add @duffcloudservices/site-forms
37
+ ```
38
+
39
+ See [`PUBLISHING.md`](./PUBLISHING.md) for the registry, version, and
40
+ release-workflow details.
41
+
42
+ ## Vite setup
43
+
44
+ Three pieces are required in the consuming site:
45
+
46
+ 1. **`vite-plugin-yaml`** so YAML modules return parsed objects:
47
+
48
+ ```ts
49
+ // vite.config.ts
50
+ import yaml from '@modyfi/vite-plugin-yaml'
51
+
52
+ export default defineConfig({
53
+ plugins: [vue(), yaml()],
54
+ })
55
+ ```
56
+
57
+ Without it, the loader falls back to parsing raw strings via
58
+ `js-yaml`, which works but pays the parse cost at boot.
59
+
60
+ 2. **A `formsModules` loader** in your site that does the
61
+ `import.meta.glob` from a path Vite can resolve (see below).
62
+
63
+ 3. **Env vars** the runtime reads:
64
+
65
+ | Variable | Purpose |
66
+ | ----------------------- | ---------------------------------------------------- |
67
+ | `VITE_DCS_PUBLIC_API` | Base URL of the DCS public API (no trailing slash). |
68
+ | `VITE_DCS_SITE_SLUG` | Default site slug used when the prop is omitted. |
69
+
70
+ ### Why the `formsModules` prop is required in real sites
71
+
72
+ `<DcsForm/>` ships with an internal `import.meta.glob('/.dcs/forms/*.yaml')`
73
+ fallback, but Vite resolves the leading `/` against the **consumer's
74
+ Vite project root** (the directory containing `vite.config.ts`).
75
+ On every customer-site repo today, the `.dcs/forms/` directory lives
76
+ at the **repo root**, one or more levels above the Vite root
77
+ (typically `site/` or `docs/`). The internal glob therefore matches
78
+ nothing and you get:
79
+
80
+ ```
81
+ [@duffcloudservices/site-forms] No form definition found for "contact".
82
+ Expected a YAML at /.dcs/forms/contact.yaml.
83
+ ```
84
+
85
+ The fix is a one-file loader the rest of your site imports from.
86
+
87
+ #### Vue SPA (`vite.config.ts` in `site/`)
88
+
89
+ ```ts
90
+ // site/src/dcs-forms.ts
91
+ const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
92
+ eager: true,
93
+ import: 'default',
94
+ })
95
+ export const dcsFormsModules: Record<string, unknown> = modules
96
+ ```
97
+
98
+ #### VitePress (`vite` block in `docs/.vitepress/config.ts`)
99
+
100
+ ```ts
101
+ // docs/.vitepress/dcs-forms-loader.ts
102
+ const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
103
+ eager: true,
104
+ import: 'default',
105
+ })
106
+ export const dcsFormsModules: Record<string, unknown> = modules
107
+ ```
108
+
109
+ The relative depth (`../../`) depends on where the loader file lives
110
+ relative to the repo root. Adjust as needed.
111
+
112
+ ## Usage
113
+
114
+ Place a YAML file at `<site>/.dcs/forms/contact.yaml`:
115
+
116
+ ```yaml
117
+ formId: contact
118
+ title: Contact Us
119
+ submission:
120
+ kind: lead
121
+ fields:
122
+ - id: name
123
+ type: text
124
+ label: Name
125
+ required: true
126
+ - id: email
127
+ type: email
128
+ label: Email
129
+ required: true
130
+ - id: message
131
+ type: textarea
132
+ label: Message
133
+ required: true
134
+ ```
135
+
136
+ Then in any page component:
137
+
138
+ ```vue
139
+ <script setup lang="ts">
140
+ import { DcsForm } from '@duffcloudservices/site-forms'
141
+ import { dcsFormsModules } from '@/dcs-forms'
142
+ </script>
143
+
144
+ <template>
145
+ <DcsForm
146
+ form-id="contact"
147
+ :forms-modules="dcsFormsModules"
148
+ @submit-success="onSuccess"
149
+ @submit-error="onError"
150
+ />
151
+ </template>
152
+ ```
153
+
154
+ ## Props
155
+
156
+ | Prop | Type | Default | Notes |
157
+ | -------------------- | --------------------------------- | -------------------------------------- | ----------------------------------------------------------- |
158
+ | `formId` | `string` (required) | — | Matches `.dcs/forms/<formId>.yaml`. |
159
+ | `siteSlug` | `string` | `import.meta.env.VITE_DCS_SITE_SLUG` | Path segment in the submission URL. |
160
+ | `definitionOverride` | `PortalFormDefinition` | — | Used by the portal preview iframe to show in-flight edits. |
161
+ | `apiBase` | `string` | `import.meta.env.VITE_DCS_PUBLIC_API` | Override for tests / non-prod environments. |
162
+ | `captchaToken` | `string` | — | Attached to the submission payload when set. |
163
+ | `formsModules` | `Record<string, unknown>` | internal fallback glob (rarely matches) | **Required in real sites.** Pass a `Record<string, unknown>` from your own `import.meta.glob('../../.dcs/forms/*.yaml', { eager: true, import: 'default' })` — see Vite setup section. |
164
+
165
+ ## Emits
166
+
167
+ | Event | Payload | When |
168
+ | ------------------ | ---------------------- | ------------------------------------------- |
169
+ | `submit-success` | `DcsFormSubmitSuccess` | API responded `2xx`. |
170
+ | `submit-error` | `DcsFormSubmitError` | Network or non-2xx response after retries. |
171
+ | `validation-error` | `FormErrors` | Submit attempted with invalid required/regex/etc. fields. |
172
+
173
+ ## Slots
174
+
175
+ Every slot exposes scoped data so consumers (KT Braun, Kept) can swap
176
+ shadcn primitives in without forking field components.
177
+
178
+ | Slot | Scope | Default |
179
+ | ---------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
180
+ | `header` | `{ definition }` | `<h2>` + description |
181
+ | `progress` | `{ current, total, step }` | `Step N of M — Title` (multi-step only) |
182
+ | `actions` | `{ isFirstStep, isLastStep, submitting, prev, next }` | Plain `<button>` elements |
183
+ | `success` | `{ definition }` | `definition.successMessage` |
184
+ | `missing` | `{ formId }` | Friendly fallback when the YAML can't be found |
185
+
186
+ Per-field components (`DcsFormText` etc.) expose `#input` slots so a
187
+ shadcn site can replace the underlying primitive while keeping the
188
+ wrapper, label, help, and error-message structure.
189
+
190
+ ## Composables
191
+
192
+ For sites that want a fully custom layout, drop `<DcsForm/>` and use
193
+ the underlying composables directly:
194
+
195
+ ```ts
196
+ import {
197
+ useDcsForm,
198
+ validateForm,
199
+ submitFormValues,
200
+ parseFormYaml,
201
+ } from '@duffcloudservices/site-forms'
202
+ ```
203
+
204
+ - `useDcsForm({ definition })` — reactive `values`, `errors`, `steps`,
205
+ `next`, `prev`, `validateAll`, `collectSubmissionValues`, etc.
206
+ - `validateForm(def, values, fieldIds?)` — pure validator usable in
207
+ any setting (server-side, tests, custom adapters).
208
+ - `submitFormValues({ apiBase, siteSlug, payload })` — one-shot POST
209
+ with a single retry on 5xx and `multipart/form-data` when files are
210
+ present.
211
+
212
+ ## Visual editor integration
213
+
214
+ The form root carries `data-form-key="<formId>"` and every field
215
+ wrapper carries `data-form-field-key="<fieldId>"`. The portal preview
216
+ iframe bridge uses these to highlight and select form regions. Do not
217
+ strip them in custom layouts.
218
+
219
+ ## Schema validation
220
+
221
+ In dev (`import.meta.env.DEV === true`) the runtime validates each
222
+ loaded definition against the JSON Schema bundled in
223
+ `src/schema/form-definition.schema.json` (snapshot of
224
+ `contracts/dist/form-definition.schema.json`) and logs failures via
225
+ `console.warn`. Production builds skip the warning to avoid noisy
226
+ end-user consoles.
227
+
228
+ When the contracts schema is regenerated (`pnpm --filter @dcs/contracts
229
+ generate`), refresh the snapshot:
230
+
231
+ ```powershell
232
+ Copy-Item ../../contracts/dist/form-definition.schema.json ./src/schema/form-definition.schema.json -Force
233
+ pnpm --filter @duffcloudservices/site-forms test --run
234
+ ```
235
+
236
+ ## Scripts
237
+
238
+ ```powershell
239
+ pnpm --filter @duffcloudservices/site-forms build # vite library build (esm + dts)
240
+ pnpm --filter @duffcloudservices/site-forms test # vitest --run
241
+ pnpm --filter @duffcloudservices/site-forms type-check # vue-tsc --noEmit
242
+ ```
243
+
244
+ ## Related docs
245
+
246
+ - **Authoring guide** — [`.docs/forms/AUTHORING.md`](../../.docs/forms/AUTHORING.md)
247
+ covers the YAML schema, worked examples, validation flow, HIPAA
248
+ guardrails, and the hand-coded → `<DcsForm/>` migration recipe.
249
+ - **Publishing** — [`PUBLISHING.md`](./PUBLISHING.md) covers the
250
+ registry, OIDC trusted publishing, version bump policy, and the
251
+ exact dep line sibling customer-site repos should add.
252
+ - **Validation CLI** — [`cli/forms/README.md`](../../cli/forms/README.md)
253
+ documents `dcs forms validate` and `dcs forms doctor`, which lint
254
+ the `.dcs/forms/*.yaml` files in a customer-site repo.
255
+
256
+ ## Ownership
257
+
258
+ Per `packages/README.md`: external/consumer-facing docs live here, not
259
+ in repo-root docs. Cross-cutting details (e.g. the public submissions
260
+ API contract) belong in `contracts/README.md`.