• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

medplum / medplum / 30567424760

30 Jul 2026 05:35PM UTC coverage: 92.022% (+0.03%) from 91.994%
30567424760

push

github

web-flow
Provider: Add Message Settings to Messages (#9621)

* feat(react): rework New Message dialog with MultiResourceInput

Replace the QuestionnaireForm-based practitioner picker with
MultiResourceInput, defaulting the signed-in provider. Make the patient
required and disable Next until a patient and at least one practitioner are
selected. Apply the shared Send-Fax-style modal spacing/font.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* feat(react): add Message Settings dialog for threads

Add EditTopicDialog, opened from a new info-icon button in the thread header,
to edit a thread's topic and practitioner participants after creation. References
are resolved in parallel before render so all fields appear together, with a
failed patient read handled independently. Threads with no practitioner recipient
fall back to showing the sender practitioner. Non-practitioner recipients are
preserved on save.

Add useThreadInbox.updateThreadParent so an edited thread updates in place rather
than refetching, keeping a draft thread (no reply yet) in the list. Order the
inbox by latest message time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* feat(react): auto-select first thread in inbox

When no thread is selected and the list has items, navigate to the first
thread, matching the Tasks/Faxes boards. Add an optional replace flag to the
Medplum navigate function (forwarded by the app router wrappers) so the implicit
selection replaces history instead of adding a back-button entry that would
re-trigger the selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Kevin Shaw <kevin@medplum.com>

* fix(react): type resolved practitioners via discriminant narrowing

The full tsc build (not run by vitest/esbuild) rejected the explicit
`PromiseFulfilledResult<Practitioner>` type predicate because readReference
resolves to WithId<Prac... (continued)

22738 of 25819 branches covered (88.07%)

Branch coverage included in aggregate %.

103 of 113 new or added lines in 10 files covered. (91.15%)

1 existing line in 1 file now uncovered.

40309 of 42694 relevant lines covered (94.41%)

12266.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.33
/packages/react/src/chat/ThreadInbox/ThreadMessageForm.tsx
1
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors
2
// SPDX-License-Identifier: Apache-2.0
3
import { Divider, Stack, Text, TextInput } from '@mantine/core';
4
import { createReference } from '@medplum/core';
5
import type { Patient, Practitioner, Reference } from '@medplum/fhirtypes';
6
import type { JSX } from 'react';
7
import { MultiResourceInput } from '../../ResourceInput/MultiResourceInput';
8
import { ResourceInput } from '../../ResourceInput/ResourceInput';
9

10
/**
11
 * Props for the ThreadMessageForm component — the shared Patient + Practitioner + Topic fields
12
 * (plus the trailing divider before the action button) used by the New Message and Message
13
 * Settings dialogs.
14
 * The patient and practitioner inputs are uncontrolled: the `default*` props seed them on
15
 * mount only, and later edits are reported through the `on*Change` callbacks. Mount the form
16
 * only once the defaults are known (both dialogs mount it after the thread is resolved).
17
 * @param defaultPractitioners - The initial practitioner recipients (init-only, uncontrolled input).
18
 * @param onPractitionersChange - Called with the new practitioner reference list when the selection changes.
19
 * @param topic - The current topic text (controlled).
20
 * @param onTopicChange - Called with the new topic text on every edit.
21
 * @param defaultPatient - The initial patient (init-only, uncontrolled input).
22
 * @param onPatientChange - Called with the new patient reference when the selection changes. When omitted, the patient field is read-only.
23
 * @param allowPatientSelection - When true, the patient field is an editable search input. When false (default), the field is pre-filled from `defaultPatient` and disabled.
24
 */
25
export interface ThreadMessageFormProps {
26
  defaultPractitioners: Reference<Practitioner>[];
27
  onPractitionersChange: (practitioners: Reference<Practitioner>[]) => void;
28
  topic: string;
29
  onTopicChange: (topic: string) => void;
30
  defaultPatient?: Reference<Patient>;
31
  onPatientChange?: (patient: Reference<Patient> | undefined) => void;
32
  allowPatientSelection?: boolean;
33
}
34

35
export const ThreadMessageForm = (props: ThreadMessageFormProps): JSX.Element => {
4✔
36
  const { defaultPractitioners, onPractitionersChange, topic, onTopicChange, defaultPatient, onPatientChange } = props;
54✔
37
  const allowPatientSelection = props.allowPatientSelection ?? false;
54✔
38
  return (
54✔
39
    <>
40
      <Stack gap={0}>
41
        <Text fw={500}>Patient</Text>
42

43
        {allowPatientSelection && <Text c="dimmed">Select a patient</Text>}
56✔
44

45
        <ResourceInput
46
          resourceType="Patient"
47
          name="patient"
48
          required={!!onPatientChange}
49
          defaultValue={defaultPatient}
50
          disabled={!allowPatientSelection && !!defaultPatient}
106✔
51
          onChange={(value) => {
NEW
52
            onPatientChange?.(value ? createReference(value) : undefined);
×
53
          }}
54
        />
55
      </Stack>
56

57
      <Stack gap={0}>
58
        <Text fw={500}>Practitioner</Text>
59
        <Text c="dimmed">Select one or more practitioners</Text>
60

61
        <MultiResourceInput<Practitioner>
62
          resourceType="Practitioner"
63
          name="practitioners"
64
          defaultValue={defaultPractitioners}
65
          onChange={(resources) =>
NEW
66
            onPractitionersChange(resources.map((practitioner) => createReference(practitioner)))
×
67
          }
68
        />
69
      </Stack>
70

71
      <Stack gap={0}>
72
        <Text fw={500}>Topic (optional)</Text>
73
        <Text c="dimmed">Enter a topic for the message</Text>
74

75
        <TextInput placeholder="Enter your topic" value={topic} onChange={(e) => onTopicChange(e.target.value)} />
25✔
76
      </Stack>
77

78
      <Divider pt="xs" />
79
    </>
80
  );
81
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc