@duffcloudservices/site-forms 0.1.4 → 0.2.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.
package/README.md CHANGED
@@ -208,6 +208,10 @@ import {
208
208
  with a single retry on 5xx and `multipart/form-data` when files are
209
209
  present.
210
210
 
211
+ File fields emit a single `File` by default. When `attachmentPolicy.maxFiles`
212
+ is greater than `1`, the file field emits `File[]`, enables multiple
213
+ selection, previews selected files, and allows removing files before submit.
214
+
211
215
  ## Visual editor integration
212
216
 
213
217
  The form root carries `data-form-key="<formId>"` and every field
@@ -251,6 +255,40 @@ pnpm --filter @duffcloudservices/site-forms test # vitest --run
251
255
  pnpm --filter @duffcloudservices/site-forms type-check # vue-tsc --noEmit
252
256
  ```
253
257
 
258
+ ## Compliance requirements for form submissions
259
+
260
+ ### Sensitive forms (`isSensitive: true`)
261
+
262
+ Forms flagged as sensitive (law firm intake, HIPAA intake, any privilege-sensitive questionnaire) carry platform-enforced rules that form authors and site integrators must not override:
263
+
264
+ 1. **Notification emails suppress submission content.** When `isSensitive: true`, the portal notification email for a new submission contains only a portal link — never field values, names, or any submission body. This is required for attorney-client privilege (ABA Rule 1.6) and HIPAA confidentiality.
265
+
266
+ 2. **Access audit logging.** Every time a portal user opens a sensitive-form submission, the platform writes an audit log entry (`PortalAuditLog`, event: `submission_viewed`). This cannot be disabled.
267
+
268
+ 3. **Set via portal, not YAML.** The `isSensitive` flag lives in the `PortalSiteForms` table (managed via the portal Form Manager). It is not part of the `.dcs/forms/*.yaml` snapshot — the runtime itself has no concept of sensitivity.
269
+
270
+ ### Form version tracking
271
+
272
+ Every submission row stores `formVersion` (the schema version of the form at submission time). If a form's fields change after submissions are collected, old submissions remain interpretable: the portal can reconstruct what was shown by looking up the version-keyed schema. This is required for compliance audit trails.
273
+
274
+ ### General form requirements
275
+
276
+ All DCS-managed forms that collect personal data must:
277
+ - Link a Privacy Policy URL in proximity to the submit action
278
+ - Store `formVersionId`, submission timestamp, and submitter IP with every submission row (handled automatically by the platform)
279
+ - Not collect unnecessary data fields (data minimization)
280
+
281
+ ### Intake questionnaires (attorneys, healthcare)
282
+
283
+ Use the **Legal Intake — Standard** form template (created via the portal Form Manager → Templates) when building intake forms for law firms. This template:
284
+ - Pre-populates the attorney-client privilege disclaimer and consent checkbox (non-removable)
285
+ - Automatically sets `isSensitive: true`
286
+ - Includes matter-type and adverse-parties fields for conflict screening
287
+
288
+ For healthcare intake, mark the form as `FormKind: "hipaa"` so the submission handler applies PHI-aware redaction for notification emails.
289
+
290
+ ---
291
+
254
292
  ## Related docs
255
293
 
256
294
  - **Authoring guide** — [`.docs/forms/AUTHORING.md`](../../.docs/forms/AUTHORING.md)
@@ -47,7 +47,13 @@ declare function __VLS_template(): {
47
47
  rootEl: any;
48
48
  };
49
49
  type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
50
- declare const __VLS_component: import('vue').DefineComponent<Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
50
+ declare const __VLS_component: import('vue').DefineComponent<Props, {
51
+ values: import('./types').FormValues;
52
+ errors: import('vue').Ref<import('./types').FormErrors, import('./types').FormErrors>;
53
+ validateAll: () => boolean;
54
+ collectSubmissionValues: () => import('./types').FormValues;
55
+ reset: () => void;
56
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
51
57
  "submit-success": (event: DcsFormSubmitSuccess) => any;
52
58
  "submit-error": (event: DcsFormSubmitError) => any;
53
59
  "validation-error": (errors: Record<string, string | undefined>) => any;
@@ -1,4 +1,4 @@
1
- import { PortalFormDefinition, PortalFormField, FormErrors, FormValues } from '../types';
1
+ import { PortalFormAttachmentPolicy, PortalFormDefinition, PortalFormField, FormErrors, FormValues } from '../types';
2
2
  /**
3
3
  * Returns true iff `field` is currently visible given the form's
4
4
  * other values. Hidden fields are never validated and never sent.
@@ -9,7 +9,7 @@ export declare function isFieldVisible(field: PortalFormField, values: FormValue
9
9
  * `undefined` if valid. Layout-only field types (section-heading,
10
10
  * html-block) and hidden fields never produce errors.
11
11
  */
12
- export declare function validateField(field: PortalFormField, value: unknown): string | undefined;
12
+ export declare function validateField(field: PortalFormField, value: unknown, attachmentPolicy?: PortalFormAttachmentPolicy): string | undefined;
13
13
  /**
14
14
  * Validates an entire form. Skips fields that are not visible.
15
15
  * If `fieldIds` is supplied, only those fields are validated
@@ -1,12 +1,15 @@
1
- import { PortalFormField } from '../types';
1
+ import { PortalFormAttachmentPolicy, PortalFormField } from '../types';
2
2
  type __VLS_Props = {
3
3
  field: PortalFormField;
4
- modelValue: File | undefined;
4
+ attachmentPolicy?: PortalFormAttachmentPolicy;
5
+ modelValue: File | File[] | undefined;
5
6
  error?: string;
6
7
  };
7
8
  declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
- "update:modelValue": (value: File | undefined) => any;
9
+ "update:modelValue": (value: File | File[] | undefined) => any;
9
10
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
10
- "onUpdate:modelValue"?: ((value: File | undefined) => any) | undefined;
11
- }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
11
+ "onUpdate:modelValue"?: ((value: File | File[] | undefined) => any) | undefined;
12
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
13
+ inputEl: HTMLInputElement;
14
+ }, HTMLDivElement>;
12
15
  export default _default;