@happyvertical/smrt-ui 0.51.39 → 0.52.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 (212) hide show
  1. package/AGENTS.md +14 -2
  2. package/README.md +257 -5
  3. package/dist/actions/__tests__/swipe-dismiss.test.js +69 -0
  4. package/dist/actions/swipe-dismiss.d.ts +42 -0
  5. package/dist/actions/swipe-dismiss.d.ts.map +1 -0
  6. package/dist/actions/swipe-dismiss.js +69 -0
  7. package/dist/components/calendar/Calendar.svelte +5 -0
  8. package/dist/components/calendar/Calendar.svelte.d.ts.map +1 -1
  9. package/dist/components/calendar/CalendarView.svelte +1250 -0
  10. package/dist/components/calendar/CalendarView.svelte.d.ts +66 -0
  11. package/dist/components/calendar/CalendarView.svelte.d.ts.map +1 -0
  12. package/dist/components/calendar/DayView.svelte +5 -0
  13. package/dist/components/calendar/DayView.svelte.d.ts.map +1 -1
  14. package/dist/components/calendar/__tests__/CalendarView.test.js +226 -0
  15. package/dist/components/calendar/__tests__/calendar-model.test.js +138 -0
  16. package/dist/components/calendar/calendar-model.d.ts +178 -0
  17. package/dist/components/calendar/calendar-model.d.ts.map +1 -0
  18. package/dist/components/calendar/calendar-model.js +362 -0
  19. package/dist/components/calendar/index.d.ts +12 -1
  20. package/dist/components/calendar/index.d.ts.map +1 -1
  21. package/dist/components/calendar/index.js +11 -1
  22. package/dist/components/data/CollectionList.svelte +23 -5
  23. package/dist/components/data/CollectionList.svelte.d.ts +8 -2
  24. package/dist/components/data/CollectionList.svelte.d.ts.map +1 -1
  25. package/dist/components/data/DataTable.svelte +118 -40
  26. package/dist/components/data/DataTable.svelte.d.ts.map +1 -1
  27. package/dist/components/data/DataTableController.d.ts +4 -0
  28. package/dist/components/data/DataTableController.d.ts.map +1 -1
  29. package/dist/components/data/DataTableController.js +11 -4
  30. package/dist/components/data/ListSortSelect.svelte +121 -0
  31. package/dist/components/data/ListSortSelect.svelte.d.ts +22 -0
  32. package/dist/components/data/ListSortSelect.svelte.d.ts.map +1 -0
  33. package/dist/components/data/SortableHeader.svelte +169 -0
  34. package/dist/components/data/SortableHeader.svelte.d.ts +30 -0
  35. package/dist/components/data/SortableHeader.svelte.d.ts.map +1 -0
  36. package/dist/components/data/__tests__/CollectionList.test.js +37 -0
  37. package/dist/components/data/__tests__/DataTable.test.js +76 -0
  38. package/dist/components/data/__tests__/DataTableController.test.js +35 -0
  39. package/dist/components/data/__tests__/SortableHeader.test.js +101 -0
  40. package/dist/components/data/__tests__/link-step-surface.test.js +172 -0
  41. package/dist/components/data/__tests__/list-sort-state.svelte.test.js +71 -0
  42. package/dist/components/data/__tests__/list-sort.test.js +91 -0
  43. package/dist/components/data/__tests__/surface-navigation.test.js +121 -0
  44. package/dist/components/data/data-surface.d.ts +1 -0
  45. package/dist/components/data/data-surface.d.ts.map +1 -1
  46. package/dist/components/data/data-surface.js +1 -0
  47. package/dist/components/data/index.d.ts +6 -0
  48. package/dist/components/data/index.d.ts.map +1 -1
  49. package/dist/components/data/index.js +6 -0
  50. package/dist/components/data/link-surface.d.ts +79 -0
  51. package/dist/components/data/link-surface.d.ts.map +1 -0
  52. package/dist/components/data/link-surface.js +155 -0
  53. package/dist/components/data/list-sort-state.svelte.d.ts +39 -0
  54. package/dist/components/data/list-sort-state.svelte.d.ts.map +1 -0
  55. package/dist/components/data/list-sort-state.svelte.js +47 -0
  56. package/dist/components/data/list-sort.d.ts +94 -0
  57. package/dist/components/data/list-sort.d.ts.map +1 -0
  58. package/dist/components/data/list-sort.js +182 -0
  59. package/dist/components/data/step-surface.d.ts +73 -0
  60. package/dist/components/data/step-surface.d.ts.map +1 -0
  61. package/dist/components/data/step-surface.js +177 -0
  62. package/dist/components/data/surface-navigation.d.ts +49 -0
  63. package/dist/components/data/surface-navigation.d.ts.map +1 -0
  64. package/dist/components/data/surface-navigation.js +100 -0
  65. package/dist/components/data/types.d.ts +19 -0
  66. package/dist/components/data/types.d.ts.map +1 -1
  67. package/dist/components/feedback/PhoneSheet.svelte +224 -0
  68. package/dist/components/feedback/PhoneSheet.svelte.d.ts +35 -0
  69. package/dist/components/feedback/PhoneSheet.svelte.d.ts.map +1 -0
  70. package/dist/components/feedback/WorkingStrip.svelte +426 -0
  71. package/dist/components/feedback/WorkingStrip.svelte.d.ts +37 -0
  72. package/dist/components/feedback/WorkingStrip.svelte.d.ts.map +1 -0
  73. package/dist/components/feedback/__tests__/PhoneSheet.test.js +101 -0
  74. package/dist/components/feedback/__tests__/WorkingStrip.test.js +172 -0
  75. package/dist/components/feedback/index.d.ts +4 -0
  76. package/dist/components/feedback/index.d.ts.map +1 -1
  77. package/dist/components/feedback/index.js +5 -0
  78. package/dist/components/feedback/working-status.d.ts +34 -0
  79. package/dist/components/feedback/working-status.d.ts.map +1 -0
  80. package/dist/components/feedback/working-status.js +5 -0
  81. package/dist/components/forms/Checkbox.svelte +26 -2
  82. package/dist/components/forms/Combobox.svelte +68 -12
  83. package/dist/components/forms/Combobox.svelte.d.ts +6 -0
  84. package/dist/components/forms/Combobox.svelte.d.ts.map +1 -1
  85. package/dist/components/forms/DictationButton.svelte +128 -0
  86. package/dist/components/forms/DictationButton.svelte.d.ts +19 -0
  87. package/dist/components/forms/DictationButton.svelte.d.ts.map +1 -0
  88. package/dist/components/forms/DictationStatus.svelte +164 -0
  89. package/dist/components/forms/DictationStatus.svelte.d.ts +11 -0
  90. package/dist/components/forms/DictationStatus.svelte.d.ts.map +1 -0
  91. package/dist/components/forms/Fieldset.svelte +5 -1
  92. package/dist/components/forms/Fieldset.svelte.d.ts +2 -0
  93. package/dist/components/forms/Fieldset.svelte.d.ts.map +1 -1
  94. package/dist/components/forms/FormActionBar.svelte +84 -0
  95. package/dist/components/forms/FormActionBar.svelte.d.ts +23 -0
  96. package/dist/components/forms/FormActionBar.svelte.d.ts.map +1 -0
  97. package/dist/components/forms/Input.svelte +9 -0
  98. package/dist/components/forms/Radio.svelte +15 -4
  99. package/dist/components/forms/SearchInput.svelte +207 -0
  100. package/dist/components/forms/SearchInput.svelte.d.ts +22 -0
  101. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  102. package/dist/components/forms/Select.svelte +9 -0
  103. package/dist/components/forms/StagedControlReview.svelte +21 -12
  104. package/dist/components/forms/StagedControlReview.svelte.d.ts.map +1 -1
  105. package/dist/components/forms/Switch.svelte +11 -5
  106. package/dist/components/forms/TagsInput.svelte +8 -1
  107. package/dist/components/forms/Textarea.svelte +8 -0
  108. package/dist/components/forms/__tests__/Combobox.test.js +87 -0
  109. package/dist/components/forms/__tests__/ControlHitArea.test.js +73 -0
  110. package/dist/components/forms/__tests__/DictationButton.test.js +176 -0
  111. package/dist/components/forms/__tests__/Fieldset.test.js +37 -0
  112. package/dist/components/forms/__tests__/FormActionBar.test.js +22 -0
  113. package/dist/components/forms/__tests__/SearchInput.test.js +92 -0
  114. package/dist/components/forms/__tests__/StagedControlReview.test.js +37 -3
  115. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte +19 -0
  116. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts +11 -0
  117. package/dist/components/forms/__tests__/combobox-labels.fixture.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/__tests__/composite-registration.fixture.svelte +29 -0
  119. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts +8 -0
  120. package/dist/components/forms/__tests__/composite-registration.fixture.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/__tests__/control-interaction.test.js +19 -1
  122. package/dist/components/forms/__tests__/control-proposals.test.js +63 -0
  123. package/dist/components/forms/__tests__/dictation-recording.test.js +582 -0
  124. package/dist/components/forms/__tests__/dictation.test.js +486 -0
  125. package/dist/components/forms/__tests__/long-press.test.js +175 -0
  126. package/dist/components/forms/__tests__/place-picker.fixture.svelte +33 -0
  127. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts +12 -0
  128. package/dist/components/forms/__tests__/place-picker.fixture.svelte.d.ts.map +1 -0
  129. package/dist/components/forms/audio-capture.d.ts +69 -0
  130. package/dist/components/forms/audio-capture.d.ts.map +1 -0
  131. package/dist/components/forms/audio-capture.js +193 -0
  132. package/dist/components/forms/control-interaction.d.ts +6 -0
  133. package/dist/components/forms/control-interaction.d.ts.map +1 -1
  134. package/dist/components/forms/control-interaction.js +12 -1
  135. package/dist/components/forms/control-proposals.d.ts +55 -0
  136. package/dist/components/forms/control-proposals.d.ts.map +1 -0
  137. package/dist/components/forms/control-proposals.js +188 -0
  138. package/dist/components/forms/dictation-transcribe.d.ts +36 -0
  139. package/dist/components/forms/dictation-transcribe.d.ts.map +1 -0
  140. package/dist/components/forms/dictation-transcribe.js +65 -0
  141. package/dist/components/forms/dictation.svelte.d.ts +200 -0
  142. package/dist/components/forms/dictation.svelte.d.ts.map +1 -0
  143. package/dist/components/forms/dictation.svelte.js +686 -0
  144. package/dist/components/forms/index.d.ts +14 -1
  145. package/dist/components/forms/index.d.ts.map +1 -1
  146. package/dist/components/forms/index.js +14 -1
  147. package/dist/components/forms/insert-text.d.ts +12 -0
  148. package/dist/components/forms/insert-text.d.ts.map +1 -0
  149. package/dist/components/forms/insert-text.js +38 -0
  150. package/dist/components/forms/long-press.d.ts +88 -0
  151. package/dist/components/forms/long-press.d.ts.map +1 -0
  152. package/dist/components/forms/long-press.js +213 -0
  153. package/dist/components/forms/ready-beep.d.ts +24 -0
  154. package/dist/components/forms/ready-beep.d.ts.map +1 -0
  155. package/dist/components/forms/ready-beep.js +82 -0
  156. package/dist/components/layout/PageHeader.svelte +151 -17
  157. package/dist/components/layout/PageHeader.svelte.d.ts +47 -7
  158. package/dist/components/layout/PageHeader.svelte.d.ts.map +1 -1
  159. package/dist/components/layout/__tests__/PageHeader.test.js +87 -1
  160. package/dist/components/layout/index.d.ts +1 -0
  161. package/dist/components/layout/index.d.ts.map +1 -1
  162. package/dist/components/layout/index.js +1 -0
  163. package/dist/components/layout/page-header-context.d.ts +25 -0
  164. package/dist/components/layout/page-header-context.d.ts.map +1 -0
  165. package/dist/components/layout/page-header-context.js +16 -0
  166. package/dist/components/nav/Tabs.svelte +241 -0
  167. package/dist/components/nav/Tabs.svelte.d.ts +15 -0
  168. package/dist/components/nav/Tabs.svelte.d.ts.map +1 -1
  169. package/dist/components/nav/__tests__/LinkTabs.test.js +117 -0
  170. package/dist/components/nav/index.d.ts +1 -0
  171. package/dist/components/nav/index.d.ts.map +1 -1
  172. package/dist/components/nav/index.js +1 -0
  173. package/dist/components/nav/tabs-overflow.d.ts +12 -0
  174. package/dist/components/nav/tabs-overflow.d.ts.map +1 -0
  175. package/dist/components/nav/tabs-overflow.js +17 -0
  176. package/dist/components/nav/types.d.ts +8 -0
  177. package/dist/components/nav/types.d.ts.map +1 -1
  178. package/dist/components/ui/Button.svelte +7 -0
  179. package/dist/components/ui/Card.svelte +14 -5
  180. package/dist/components/ui/Dropdown.svelte +55 -1
  181. package/dist/components/ui/Dropdown.svelte.d.ts +8 -0
  182. package/dist/components/ui/Dropdown.svelte.d.ts.map +1 -1
  183. package/dist/components/ui/IconToggle.svelte +139 -0
  184. package/dist/components/ui/IconToggle.svelte.d.ts +32 -0
  185. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -0
  186. package/dist/components/ui/IconToggleGroup.svelte +56 -0
  187. package/dist/components/ui/IconToggleGroup.svelte.d.ts +21 -0
  188. package/dist/components/ui/IconToggleGroup.svelte.d.ts.map +1 -0
  189. package/dist/components/ui/Pagination.svelte +1 -1
  190. package/dist/components/ui/Popover.svelte +17 -1
  191. package/dist/components/ui/Popover.svelte.d.ts +8 -0
  192. package/dist/components/ui/Popover.svelte.d.ts.map +1 -1
  193. package/dist/components/ui/__tests__/IconToggle.test.js +59 -0
  194. package/dist/components/ui/__tests__/IconToggleGroup.test.js +30 -0
  195. package/dist/components/ui/__tests__/Popover.test.js +44 -0
  196. package/dist/components/ui/__tests__/dropdown-popover-icon.test.js +125 -0
  197. package/dist/components/ui/__tests__/phone-touch-targets.test.js +39 -0
  198. package/dist/components/ui/index.d.ts +2 -0
  199. package/dist/components/ui/index.d.ts.map +1 -1
  200. package/dist/components/ui/index.js +2 -0
  201. package/dist/i18n/strings.ui.d.ts +41 -0
  202. package/dist/i18n/strings.ui.d.ts.map +1 -1
  203. package/dist/i18n/strings.ui.js +46 -0
  204. package/dist/svelte/__tests__/playground.test.js +1 -0
  205. package/dist/svelte/playground/CalendarPreview.svelte +140 -0
  206. package/dist/svelte/playground/CalendarPreview.svelte.d.ts +4 -0
  207. package/dist/svelte/playground/CalendarPreview.svelte.d.ts.map +1 -0
  208. package/dist/svelte/playground.d.ts.map +1 -1
  209. package/dist/svelte/playground.js +10 -0
  210. package/dist/types-generic.d.ts +2 -0
  211. package/dist/types-generic.d.ts.map +1 -1
  212. package/package.json +3 -3
@@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
4
  import { expectNoA11yViolations } from '../../../test-support/a11y';
5
5
  import { createControlInteractionRegistry, executeLocalControlBatch, executeLocalControlCommand, } from '../control-interaction.js';
6
+ import StagedControlReview from '../StagedControlReview.svelte';
6
7
  import CompositeUserEditFixture from './composite-user-edit.fixture.svelte';
7
8
  import SelectFixture from './select-interaction.fixture.svelte';
8
9
  import Fixture from './staged-review.fixture.svelte';
@@ -77,7 +78,8 @@ describe('StagedControlReview', () => {
77
78
  await waitFor(() => expect(field).toHaveAttribute('data-smrt-staged', 'true'));
78
79
  expect(screen.getByRole('region', { name: 'Review proposed changes' })).toBeInTheDocument();
79
80
  expect(screen.getByText(/Proposed by assistant/)).toBeInTheDocument();
80
- expect(screen.getByText('profile/display-name')).toBeInTheDocument();
81
+ // Plain field labels only: registry addressing never shows (#forms-qa).
82
+ expect(screen.queryByText(/profile\/display-name/)).not.toBeInTheDocument();
81
83
  const proposal = screen.getByRole('textbox', {
82
84
  name: 'Edit proposed value for Display name',
83
85
  });
@@ -93,6 +95,38 @@ describe('StagedControlReview', () => {
93
95
  expect(field).toHaveFocus();
94
96
  await expectNoA11yViolations(container);
95
97
  });
98
+ it('shows plain field labels, never registry ids, for unlabeled controls', async () => {
99
+ const registry = createReviewRegistry();
100
+ let town = '';
101
+ registry.register({
102
+ identity: {
103
+ formId: 'setup-network',
104
+ controlId: 'setup-network/town_name',
105
+ subject: { type: 'site', id: 'site-1', label: 'Lacombe News' },
106
+ },
107
+ metadata: { kind: 'text' },
108
+ getValue: () => town,
109
+ setValue: (next) => {
110
+ town = String(next);
111
+ },
112
+ });
113
+ render(StagedControlReview, {
114
+ props: { registry, formId: 'setup-network' },
115
+ });
116
+ await registry.execute({
117
+ action: 'stage',
118
+ identity: {
119
+ formId: 'setup-network',
120
+ controlId: 'setup-network/town_name',
121
+ subject: { type: 'site', id: 'site-1' },
122
+ },
123
+ value: 'Lacombe',
124
+ }, { source: 'agent' });
125
+ await waitFor(() => expect(screen.getByText('Town name')).toBeVisible());
126
+ expect(screen.getByText('Lacombe News')).toBeVisible();
127
+ expect(screen.queryByText(/setup-network/)).not.toBeInTheDocument();
128
+ expect(screen.queryByText(/site-1/)).not.toBeInTheDocument();
129
+ });
96
130
  it('keeps Enter in the proposal editor from submitting the form', async () => {
97
131
  const registry = createReviewRegistry();
98
132
  render(Fixture, { props: { registry } });
@@ -594,8 +628,8 @@ describe('StagedControlReview', () => {
594
628
  value: 'Grace',
595
629
  }, { source: 'agent' });
596
630
  await waitFor(() => expect(renderedFirst).toHaveAttribute('data-smrt-staged', 'true'));
597
- expect(screen.getByText('profile/display-name · record:one')).toBeVisible();
598
- expect(screen.getByText('profile/display-name · record:two')).toBeVisible();
631
+ expect(screen.queryByText(/profile\/display-name/)).not.toBeInTheDocument();
632
+ expect(screen.queryByText(/record:one/)).not.toBeInTheDocument();
599
633
  for (const subjectId of ['one', 'two']) {
600
634
  const actionName = `Display name record:${subjectId} [identity:["profile","display-name","record","${subjectId}"]]`;
601
635
  expect(screen.getByRole('textbox', {
@@ -0,0 +1,19 @@
1
+ <script lang="ts">
2
+ import Combobox from '../Combobox.svelte';
3
+ import type { ControlOption } from '../control-interaction.js';
4
+
5
+ let {
6
+ options = [],
7
+ value = $bindable(''),
8
+ valueLabel = undefined,
9
+ required = false,
10
+ }: {
11
+ options?: ControlOption[];
12
+ value?: string;
13
+ valueLabel?: string;
14
+ required?: boolean;
15
+ } = $props();
16
+ </script>
17
+
18
+ <Combobox label="Town" name="town" {options} {valueLabel} {required} bind:value />
19
+ <p data-testid="value">{value}</p>
@@ -0,0 +1,11 @@
1
+ import type { ControlOption } from '../control-interaction.js';
2
+ type $$ComponentProps = {
3
+ options?: ControlOption[];
4
+ value?: string;
5
+ valueLabel?: string;
6
+ required?: boolean;
7
+ };
8
+ declare const ComboboxLabels: import("svelte").Component<$$ComponentProps, {}, "value">;
9
+ type ComboboxLabels = ReturnType<typeof ComboboxLabels>;
10
+ export default ComboboxLabels;
11
+ //# sourceMappingURL=combobox-labels.fixture.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combobox-labels.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/combobox-labels.fixture.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE9D,KAAK,gBAAgB,GAAI;IACxB,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAkBF,QAAA,MAAM,cAAc,2DAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { ControlInteractionRegistry } from '../control-interaction.js';
3
+ import Form from '../Form.svelte';
4
+ import FormGroup from '../FormGroup.svelte';
5
+ import Input from '../Input.svelte';
6
+ import PlacePicker from './place-picker.fixture.svelte';
7
+
8
+ interface Props {
9
+ registry: ControlInteractionRegistry;
10
+ }
11
+ let { registry }: Props = $props();
12
+ let name = $state('');
13
+ let place = $state<{
14
+ name: string;
15
+ latitude: number;
16
+ longitude: number;
17
+ } | null>(null);
18
+ </script>
19
+
20
+ <Form formId="new-site" interactionRegistry={registry} aria-label="New site">
21
+ <FormGroup label="Site name" id="site-name">
22
+ <Input name="name" bind:value={name} />
23
+ </FormGroup>
24
+ <PlacePicker bind:value={place} />
25
+ <FormGroup label="Password" id="site-password">
26
+ <Input name="password" type="password" value="" />
27
+ </FormGroup>
28
+ </Form>
29
+ <p data-testid="place">{place ? place.name : 'none'}</p>
@@ -0,0 +1,8 @@
1
+ import type { ControlInteractionRegistry } from '../control-interaction.js';
2
+ interface Props {
3
+ registry: ControlInteractionRegistry;
4
+ }
5
+ declare const CompositeRegistration: import("svelte").Component<Props, {}, "">;
6
+ type CompositeRegistration = ReturnType<typeof CompositeRegistration>;
7
+ export default CompositeRegistration;
8
+ //# sourceMappingURL=composite-registration.fixture.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composite-registration.fixture.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/forms/__tests__/composite-registration.fixture.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAO5E,UAAU,KAAK;IACb,QAAQ,EAAE,0BAA0B,CAAC;CACtC;AA8BD,QAAA,MAAM,qBAAqB,2CAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
@@ -2610,13 +2610,31 @@ describe('control interaction registry', () => {
2610
2610
  reveal,
2611
2611
  highlight,
2612
2612
  });
2613
- await registry.execute({ action: 'focus', identity });
2613
+ await registry.execute({ action: 'focus', identity }, { source: 'user' });
2614
2614
  await registry.execute({ action: 'reveal', identity });
2615
2615
  await registry.execute({ action: 'highlight', identity, durationMs: 400 });
2616
2616
  expect(focus).toHaveBeenCalledOnce();
2617
2617
  expect(reveal).toHaveBeenCalledOnce();
2618
2618
  expect(highlight).toHaveBeenCalledWith(400);
2619
2619
  });
2620
+ it('never moves keyboard focus for an agent: focus reveals and highlights instead', async () => {
2621
+ const focus = vi.fn();
2622
+ const reveal = vi.fn();
2623
+ const highlight = vi.fn();
2624
+ const registry = createControlInteractionRegistry();
2625
+ registry.register({
2626
+ identity,
2627
+ metadata: { kind: 'text' },
2628
+ focus,
2629
+ reveal,
2630
+ highlight,
2631
+ });
2632
+ const result = await registry.execute({ action: 'focus', identity }, { source: 'agent' });
2633
+ expect(result.ok).toBe(true);
2634
+ expect(focus).not.toHaveBeenCalled();
2635
+ expect(reveal).toHaveBeenCalledOnce();
2636
+ expect(highlight).toHaveBeenCalledOnce();
2637
+ });
2620
2638
  it('rejects a recognized factory event retained beyond its dispatch', async () => {
2621
2639
  let captured;
2622
2640
  let value = 'Ada';
@@ -0,0 +1,63 @@
1
+ import { render, screen } from '@testing-library/svelte';
2
+ import { tick } from 'svelte';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createControlInteractionRegistry } from '../control-interaction.js';
5
+ import { controlProposalInputSchema, controlProposalToolName, stageControlProposals, } from '../control-proposals.js';
6
+ import Fixture from './composite-registration.fixture.svelte';
7
+ describe('useControlRegistration (public) and control proposals', () => {
8
+ it('registers a composite as one control with its declared value schema', async () => {
9
+ const registry = createControlInteractionRegistry();
10
+ render(Fixture, { props: { registry } });
11
+ await tick();
12
+ const ids = registry
13
+ .list('new-site')
14
+ .map((snapshot) => snapshot.identity.controlId);
15
+ expect(ids).toEqual(expect.arrayContaining(['name', 'place', 'password']));
16
+ const place = registry.get({ formId: 'new-site', controlId: 'place' });
17
+ expect(place?.metadata.label).toBe('Town location');
18
+ expect(place?.metadata.valueSchema).toMatchObject({ type: 'object' });
19
+ });
20
+ it('builds a proposal schema from every proposable control, never secrets', async () => {
21
+ const registry = createControlInteractionRegistry();
22
+ render(Fixture, { props: { registry } });
23
+ await tick();
24
+ const schema = controlProposalInputSchema(registry, 'new-site');
25
+ expect(Object.keys(schema.properties).sort()).toEqual(['name', 'place']);
26
+ expect(schema.properties.name).toEqual({
27
+ type: 'string',
28
+ title: 'Site name',
29
+ });
30
+ expect(schema.properties.place).toMatchObject({
31
+ type: 'object',
32
+ title: 'Town location',
33
+ });
34
+ expect(controlProposalInputSchema(registry, 'new-site', { exclude: ['name'] })).toMatchObject({ properties: { place: expect.any(Object) } });
35
+ });
36
+ it('stages proposals for review without changing values', async () => {
37
+ const registry = createControlInteractionRegistry();
38
+ render(Fixture, { props: { registry } });
39
+ await tick();
40
+ const result = await stageControlProposals(registry, 'new-site', {
41
+ name: 'Lacombe',
42
+ place: { name: 'Lacombe', latitude: 52.46, longitude: -113.73 },
43
+ password: 'hunter2',
44
+ nope: 1,
45
+ }, { actorId: 'assistant' });
46
+ expect(result).toEqual({
47
+ ok: true,
48
+ staged: 2,
49
+ rejected: [],
50
+ skipped: ['password', 'nope'],
51
+ });
52
+ expect(screen.getByTestId('place')).toHaveTextContent('none');
53
+ const staged = registry.get({ formId: 'new-site', controlId: 'place' });
54
+ expect(staged?.state.staged?.provenance).toMatchObject({
55
+ source: 'agent',
56
+ actorId: 'assistant',
57
+ });
58
+ });
59
+ it('names the tool with tool-safe characters', () => {
60
+ expect(controlProposalToolName('setup-network')).toBe('setup_network_stage_changes');
61
+ expect(controlProposalToolName('--')).toBe('form_stage_changes');
62
+ });
63
+ });