@qoretechnologies/reqraft 0.10.13-pr.74.gf718c08 → 0.10.14

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 (138) hide show
  1. package/.claude/CLAUDE.md +8 -11
  2. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  3. package/dist/components/form/engine/CompactToolbar.js +2 -2
  4. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  5. package/dist/components/form/engine/FormEngine.d.ts +15 -3
  6. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  7. package/dist/components/form/engine/FormEngine.js +15 -20
  8. package/dist/components/form/engine/FormEngine.js.map +1 -1
  9. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  10. package/dist/components/form/engine/compactRowStyles.js +3 -8
  11. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  12. package/dist/helpers/common.d.ts +0 -5
  13. package/dist/helpers/common.d.ts.map +1 -1
  14. package/dist/helpers/common.js +1 -20
  15. package/dist/helpers/common.js.map +1 -1
  16. package/dist/index.d.ts +0 -5
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -7
  19. package/dist/index.js.map +1 -1
  20. package/dist/providers/StorageProvider.d.ts +13 -0
  21. package/dist/providers/StorageProvider.d.ts.map +1 -1
  22. package/dist/providers/StorageProvider.js +29 -5
  23. package/dist/providers/StorageProvider.js.map +1 -1
  24. package/package.json +2 -2
  25. package/src/components/form/engine/CompactToolbar.tsx +117 -110
  26. package/src/components/form/engine/FormEngine.stories.tsx +27 -0
  27. package/src/components/form/engine/FormEngine.tsx +44 -25
  28. package/src/components/form/engine/compactRowStyles.ts +3 -5
  29. package/src/helpers/common.ts +0 -19
  30. package/src/hooks/useStorage/useStorage.stories.tsx +52 -0
  31. package/src/index.tsx +0 -5
  32. package/src/providers/StorageProvider.tsx +30 -4
  33. package/dist/components/composer/Composer.d.ts +0 -108
  34. package/dist/components/composer/Composer.d.ts.map +0 -1
  35. package/dist/components/composer/Composer.js +0 -238
  36. package/dist/components/composer/Composer.js.map +0 -1
  37. package/dist/components/composer/helpers.d.ts +0 -21
  38. package/dist/components/composer/helpers.d.ts.map +0 -1
  39. package/dist/components/composer/helpers.js +0 -75
  40. package/dist/components/composer/helpers.js.map +0 -1
  41. package/dist/components/composer/index.d.ts +0 -3
  42. package/dist/components/composer/index.d.ts.map +0 -1
  43. package/dist/components/composer/index.js +0 -19
  44. package/dist/components/composer/index.js.map +0 -1
  45. package/dist/components/imageLightbox/ImageLightbox.d.ts +0 -17
  46. package/dist/components/imageLightbox/ImageLightbox.d.ts.map +0 -1
  47. package/dist/components/imageLightbox/ImageLightbox.js +0 -129
  48. package/dist/components/imageLightbox/ImageLightbox.js.map +0 -1
  49. package/dist/components/imageLightbox/index.d.ts +0 -2
  50. package/dist/components/imageLightbox/index.d.ts.map +0 -1
  51. package/dist/components/imageLightbox/index.js +0 -18
  52. package/dist/components/imageLightbox/index.js.map +0 -1
  53. package/dist/components/supportTicket/AttachmentChips.d.ts +0 -10
  54. package/dist/components/supportTicket/AttachmentChips.d.ts.map +0 -1
  55. package/dist/components/supportTicket/AttachmentChips.js +0 -119
  56. package/dist/components/supportTicket/AttachmentChips.js.map +0 -1
  57. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +0 -35
  58. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +0 -1
  59. package/dist/components/supportTicket/InterfaceReferenceTags.js +0 -31
  60. package/dist/components/supportTicket/InterfaceReferenceTags.js.map +0 -1
  61. package/dist/components/supportTicket/ReferencePicker.d.ts +0 -41
  62. package/dist/components/supportTicket/ReferencePicker.d.ts.map +0 -1
  63. package/dist/components/supportTicket/ReferencePicker.js +0 -86
  64. package/dist/components/supportTicket/ReferencePicker.js.map +0 -1
  65. package/dist/components/supportTicket/TicketHeader.d.ts +0 -86
  66. package/dist/components/supportTicket/TicketHeader.d.ts.map +0 -1
  67. package/dist/components/supportTicket/TicketHeader.js +0 -149
  68. package/dist/components/supportTicket/TicketHeader.js.map +0 -1
  69. package/dist/components/supportTicket/TicketMetaTags.d.ts +0 -32
  70. package/dist/components/supportTicket/TicketMetaTags.d.ts.map +0 -1
  71. package/dist/components/supportTicket/TicketMetaTags.js +0 -21
  72. package/dist/components/supportTicket/TicketMetaTags.js.map +0 -1
  73. package/dist/components/supportTicket/TicketReplyBox.d.ts +0 -63
  74. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +0 -1
  75. package/dist/components/supportTicket/TicketReplyBox.js +0 -115
  76. package/dist/components/supportTicket/TicketReplyBox.js.map +0 -1
  77. package/dist/components/supportTicket/fileToAttachment.d.ts +0 -9
  78. package/dist/components/supportTicket/fileToAttachment.d.ts.map +0 -1
  79. package/dist/components/supportTicket/fileToAttachment.js +0 -28
  80. package/dist/components/supportTicket/fileToAttachment.js.map +0 -1
  81. package/dist/components/supportTicket/index.d.ts +0 -9
  82. package/dist/components/supportTicket/index.d.ts.map +0 -1
  83. package/dist/components/supportTicket/index.js +0 -25
  84. package/dist/components/supportTicket/index.js.map +0 -1
  85. package/dist/components/supportTicket/meta.d.ts +0 -63
  86. package/dist/components/supportTicket/meta.d.ts.map +0 -1
  87. package/dist/components/supportTicket/meta.js +0 -79
  88. package/dist/components/supportTicket/meta.js.map +0 -1
  89. package/dist/components/ticketReferences/TicketReferences.d.ts +0 -52
  90. package/dist/components/ticketReferences/TicketReferences.d.ts.map +0 -1
  91. package/dist/components/ticketReferences/TicketReferences.js +0 -453
  92. package/dist/components/ticketReferences/TicketReferences.js.map +0 -1
  93. package/dist/components/ticketReferences/index.d.ts +0 -3
  94. package/dist/components/ticketReferences/index.d.ts.map +0 -1
  95. package/dist/components/ticketReferences/index.js +0 -6
  96. package/dist/components/ticketReferences/index.js.map +0 -1
  97. package/dist/components/ticketThread/TicketThread.d.ts +0 -52
  98. package/dist/components/ticketThread/TicketThread.d.ts.map +0 -1
  99. package/dist/components/ticketThread/TicketThread.js +0 -248
  100. package/dist/components/ticketThread/TicketThread.js.map +0 -1
  101. package/dist/components/ticketThread/index.d.ts +0 -3
  102. package/dist/components/ticketThread/index.d.ts.map +0 -1
  103. package/dist/components/ticketThread/index.js +0 -7
  104. package/dist/components/ticketThread/index.js.map +0 -1
  105. package/dist/helpers/scrollbar.d.ts +0 -2
  106. package/dist/helpers/scrollbar.d.ts.map +0 -1
  107. package/dist/helpers/scrollbar.js +0 -25
  108. package/dist/helpers/scrollbar.js.map +0 -1
  109. package/src/components/composer/Composer.stories.tsx +0 -238
  110. package/src/components/composer/Composer.tsx +0 -545
  111. package/src/components/composer/helpers.ts +0 -74
  112. package/src/components/composer/index.ts +0 -2
  113. package/src/components/imageLightbox/ImageLightbox.stories.tsx +0 -156
  114. package/src/components/imageLightbox/ImageLightbox.tsx +0 -169
  115. package/src/components/imageLightbox/index.ts +0 -1
  116. package/src/components/supportTicket/AttachmentChips.stories.tsx +0 -122
  117. package/src/components/supportTicket/AttachmentChips.tsx +0 -142
  118. package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +0 -134
  119. package/src/components/supportTicket/InterfaceReferenceTags.tsx +0 -85
  120. package/src/components/supportTicket/ReferencePicker.stories.tsx +0 -296
  121. package/src/components/supportTicket/ReferencePicker.tsx +0 -237
  122. package/src/components/supportTicket/TicketConversation.stories.tsx +0 -122
  123. package/src/components/supportTicket/TicketHeader.stories.tsx +0 -183
  124. package/src/components/supportTicket/TicketHeader.tsx +0 -247
  125. package/src/components/supportTicket/TicketMetaTags.stories.tsx +0 -103
  126. package/src/components/supportTicket/TicketMetaTags.tsx +0 -85
  127. package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -355
  128. package/src/components/supportTicket/TicketReplyBox.tsx +0 -146
  129. package/src/components/supportTicket/fileToAttachment.ts +0 -24
  130. package/src/components/supportTicket/index.ts +0 -8
  131. package/src/components/supportTicket/meta.ts +0 -145
  132. package/src/components/ticketReferences/TicketReferences.stories.tsx +0 -301
  133. package/src/components/ticketReferences/TicketReferences.tsx +0 -899
  134. package/src/components/ticketReferences/index.ts +0 -2
  135. package/src/components/ticketThread/TicketThread.stories.tsx +0 -751
  136. package/src/components/ticketThread/TicketThread.tsx +0 -650
  137. package/src/components/ticketThread/index.ts +0 -6
  138. package/src/helpers/scrollbar.ts +0 -32
@@ -21,13 +21,13 @@ import {
21
21
  } from '@qoretechnologies/reqore/dist/components/Panel';
22
22
  import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
23
23
  import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
24
- import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
25
24
  import {
26
25
  changeDarkness,
27
26
  getMainBackgroundColor,
28
27
  getReadableColor,
29
28
  percentToHexAlpha,
30
29
  } from '@qoretechnologies/reqore/dist/helpers/colors';
30
+ import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
31
31
  import {
32
32
  IQorusFormField,
33
33
  IQorusFormFieldMessage,
@@ -48,11 +48,10 @@ import reduce from 'lodash/reduce';
48
48
  import size from 'lodash/size';
49
49
  import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
50
50
  import { useMeasure, useMount, useUpdateEffect } from 'react-use';
51
- import { createContext } from 'use-context-selector';
52
51
  import styled from 'styled-components';
52
+ import { createContext } from 'use-context-selector';
53
53
  import { getDefaultValue, insertAtIndex, richtextToString } from '../../../helpers/common';
54
54
  import { getRequiredOptionMessage } from '../../../helpers/options';
55
- import { query } from '../../../utils/fetch';
56
55
  import {
57
56
  IValidationResult,
58
57
  hasAllDependenciesFullfilled,
@@ -61,6 +60,7 @@ import {
61
60
  } from '../../../helpers/validations';
62
61
  import { useQorusTypes } from '../../../hooks/useQorusTypes';
63
62
  import { useTemplates } from '../../../hooks/useTemplates';
63
+ import { query } from '../../../utils/fetch';
64
64
  import { Description } from '../../Description';
65
65
  import { FocusedEditing } from '../../FocusedEditing';
66
66
  import { SelectFormField } from '../fields/select/Select';
@@ -72,12 +72,6 @@ import {
72
72
  } from '../fields/template/TemplateField';
73
73
  import { CompactRow } from './CompactRow';
74
74
  import { CompactRowContext, ICompactRowContext } from './compactRowContext';
75
- import { CompactToolbar } from './CompactToolbar';
76
- import {
77
- CompactToolbarContext,
78
- ICompactToolbarContext,
79
- TCompactSort,
80
- } from './compactToolbarContext';
81
75
  import {
82
76
  GROUP_INDENT,
83
77
  LABEL_COL_MAX,
@@ -91,9 +85,16 @@ import {
91
85
  StyledStatusBox,
92
86
  StyledStatusBoxGroupLabel,
93
87
  } from './compactRowStyles';
88
+ import { CompactToolbar } from './CompactToolbar';
89
+ import {
90
+ CompactToolbarContext,
91
+ ICompactToolbarContext,
92
+ TCompactSort,
93
+ } from './compactToolbarContext';
94
94
  import { OptionFieldMessages } from './OptionFieldMessages';
95
95
  import { OptionsHelpDialog } from './OptionsHelpDialog';
96
96
  import {
97
+ TReadFirstStatus,
97
98
  getFirstAttentionOptionName,
98
99
  getOptionGroup,
99
100
  getOptionGroupLabel,
@@ -101,7 +102,6 @@ import {
101
102
  getReadFirstCompletion,
102
103
  getReadFirstStatus,
103
104
  isOptionValueEmpty,
104
- TReadFirstStatus,
105
105
  } from './readFirst';
106
106
 
107
107
  // Re-export types for consumers
@@ -201,13 +201,6 @@ const StyledCompactWrap = styled.div<{ $flush?: boolean }>`
201
201
  engage instead of overflowing the container horizontally. */
202
202
  min-width: 0;
203
203
  max-width: 100%;
204
- /* A bit of horizontal breathing room for the whole form (header + content
205
- alike). Horizontal only — top padding would break the sticky toolbar's
206
- flush pin (see the scroll-context note below). Dropped to flush via the
207
- compactFlush prop, for embeds that own their own gutters (e.g. the
208
- SchemaDefinition tab body). */
209
- padding: ${({ $flush }) => ($flush ? '0' : '0 12px')};
210
-
211
204
  /* Own our scroll context instead of borrowing the host's. The sticky toolbar
212
205
  pins to whatever scrolls; if that scroller carries top padding (e.g. a
213
206
  ReqorePanel/ReqoreContent body), sticky \`top: 0\` resolves against its
@@ -525,6 +518,18 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
525
518
  * transparently inside the parent's edit card. Default `false`.
526
519
  */
527
520
  compactNested?: boolean;
521
+ /**
522
+ * Compact mode only: props forwarded to the read-first form's outer
523
+ * `ReqorePanel` — the panel that carries the sticky toolbar header and holds
524
+ * the status boxes. Spread AFTER the engine's own defaults, so anything set
525
+ * here wins (`flat`, `raised`, `minimal`, `stickyHeader`, `label`, `icon`,
526
+ * `intent`, `padded`, `size`, …). Two props merge instead of replacing, so a
527
+ * consumer can add to the panel without dismantling the form:
528
+ * - `actions` — appended after the engine's toolbar action.
529
+ * - `contentStyle` — merged over the engine's flex-column layout.
530
+ * No-op in classic (non-compact) mode.
531
+ */
532
+ compactPanelProps?: IReqorePanelProps;
528
533
  /** Compact mode only: per-group display metadata (label / icon / subtitle /
529
534
  * order) — the server only sends the bare group key. */
530
535
  groups?: Record<string, IFormEngineGroup>;
@@ -609,6 +614,7 @@ export const FormEngine = ({
609
614
  compact,
610
615
  compactFlush = false,
611
616
  compactNested = false,
617
+ compactPanelProps,
612
618
  commitMode = 'immediate',
613
619
  expandMode = 'single',
614
620
  onCommit,
@@ -1709,8 +1715,13 @@ export const FormEngine = ({
1709
1715
  <>
1710
1716
  {(() => {
1711
1717
  const schemaMsgs = (
1712
- suppressSchemaMessages ? [] : (options?.[optionName] as any)?.messages || []
1713
- ) as { intent?: string; title?: string; content?: string }[];
1718
+ suppressSchemaMessages ?
1719
+ []
1720
+ : (options?.[optionName] as any)?.messages || []) as {
1721
+ intent?: string;
1722
+ title?: string;
1723
+ content?: string;
1724
+ }[];
1714
1725
  if (!schemaMsgs.length) return null;
1715
1726
  const items = schemaMsgs.map(({ intent, title, content }, index) => (
1716
1727
  <ReqoreMessage
@@ -2114,7 +2125,13 @@ export const FormEngine = ({
2114
2125
  ]
2115
2126
  );
2116
2127
 
2117
- const compactHeaderActions = useMemo(() => [{ as: CompactToolbar, responsive: false }], []);
2128
+ // The engine's own toolbar action, plus whatever the consumer added through
2129
+ // `compactPanelProps.actions` — appended, so an outside action can never
2130
+ // knock the form's toolbar out of the header.
2131
+ const compactHeaderActions = useMemo(
2132
+ () => [{ as: CompactToolbar, responsive: false }, ...(compactPanelProps?.actions || [])],
2133
+ [compactPanelProps?.actions]
2134
+ );
2118
2135
  const renderCompact = () => {
2119
2136
  const headerBg = `${changeDarkness(getMainBackgroundColor(theme), 0.02)}${percentToHexAlpha(88)}`;
2120
2137
  // Toolbar filters narrow the listed rows; the meter reflects the full set.
@@ -2357,22 +2374,24 @@ export const FormEngine = ({
2357
2374
  $headerBg={compactNested ? 'transparent' : headerBg}
2358
2375
  $nested={compactNested}
2359
2376
  flat
2377
+ raised
2378
+ minimal
2360
2379
  // No panel background: the form sits transparently on whatever
2361
2380
  // hosts it (page, drawer, or — for an arg_schema field — the
2362
2381
  // parent's edit card) instead of stacking its own dark surface.
2363
2382
  // The status boxes keep their own tints; the sticky toolbar keeps
2364
2383
  // its blurred header via the $headerBg override.
2365
- transparent
2366
2384
  stickyHeader={!compactNested}
2367
- padded={false}
2385
+ // Consumer overrides land last so they win over every default
2386
+ // above; `actions` and `contentStyle` below merge rather than
2387
+ // replace (see `compactPanelProps`).
2388
+ {...compactPanelProps}
2368
2389
  actions={compactHeaderActions}
2369
2390
  contentStyle={{
2370
2391
  display: 'flex',
2371
2392
  flexFlow: 'column',
2372
2393
  gap: '10px',
2373
- // Nested sub-form: no surrounding panel padding (it's flush in
2374
- // the parent card); top-level keeps a small bottom gutter.
2375
- padding: compactNested ? '0' : '0 0 12px',
2394
+ ...compactPanelProps?.contentStyle,
2376
2395
  }}
2377
2396
  >
2378
2397
  {size(groupKeys) === 0 ?
@@ -47,14 +47,13 @@ export const StyledCompactPanel = styled(ReqorePanel)<{
47
47
  $nested?: boolean;
48
48
  }>`
49
49
  > .reqore-panel-title {
50
- background: ${({ $headerBg }) => $headerBg};
51
50
  /* The blur + translateZ exist only to make the STICKY top-level toolbar ghost
52
51
  content beneath it; a nested sub-form's header isn't sticky, so skip them
53
52
  (and the stacking context translateZ creates). */
54
53
  ${({ $nested }) =>
55
- $nested ?
56
- ''
57
- : 'backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); transform: translateZ(0);'}
54
+ $nested ? '' : (
55
+ 'backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); transform: translateZ(0);'
56
+ )}
58
57
  padding-top: ${GAP_FROM_SIZE[HEADER_GAP]}px;
59
58
  padding-bottom: ${GAP_FROM_SIZE[HEADER_GAP]}px;
60
59
  }
@@ -625,5 +624,4 @@ export const StyledGroupBody = styled.div<{
625
624
  padding-top: 0;
626
625
  padding-bottom: 0;
627
626
  }
628
-
629
627
  `;
@@ -174,25 +174,6 @@ export const splitByteSize = (value?: string): [number | undefined, string | und
174
174
  return [amount?.[0] ? Number(amount[0]) : undefined, unit?.[0]];
175
175
  };
176
176
 
177
- /** Human-readable file size, e.g. `"12.3 KB"` — whole bytes below 1 KiB, one
178
- * decimal above. Shared by the ticket thread / references attachment rows. */
179
- export const formatBytes = (bytes: number): string => {
180
- if (bytes < 1024) {
181
- return `${bytes} B`;
182
- }
183
- const units = ['KB', 'MB', 'GB'];
184
- let value = bytes / 1024;
185
- let unit = 0;
186
- while (value >= 1024 && unit < units.length - 1) {
187
- value /= 1024;
188
- unit += 1;
189
- }
190
- return `${value.toFixed(1)} ${units[unit]}`;
191
- };
192
-
193
- /** True when a MIME content type is an image (`image/*`). */
194
- export const isImage = (contentType: string): boolean => contentType.startsWith('image/');
195
-
196
177
  // URL value splitting (IDE `urlField.tsx` behavior). Lives here rather than
197
178
  // in the Url component so `validations.ts` can use it without pulling the
198
179
  // component chain into non-DOM consumers (jest).
@@ -1,5 +1,6 @@
1
1
  import { ReqoreButton, ReqoreControlGroup, ReqoreP } from '@qoretechnologies/reqore';
2
2
  import { StoryObj } from '@storybook/react-vite';
3
+ import { useRef } from 'react';
3
4
  import { fireEvent, within } from 'storybook/test';
4
5
  import { storiesStorageMock } from '../../../__tests__/ mock';
5
6
  import { testsWaitForText } from '../../../__tests__/utils';
@@ -93,6 +94,57 @@ export const ValueCanBeUpdated: Story = {
93
94
  },
94
95
  };
95
96
 
97
+ /**
98
+ * Regression for the "stale updater clobbers other keys" bug: the storage
99
+ * updater must persist onto the LATEST storage blob, not the one captured in
100
+ * its closure. Long-lived callers (an imperative store subscription, a ref to
101
+ * the mount-time setter) hold an old updater; writing through it must not wipe
102
+ * keys other hooks wrote in the meantime.
103
+ */
104
+ export const StaleUpdaterDoesNotClobberOtherKeys: Story = {
105
+ args: { reqraftOptions: { waitForStorage: true } },
106
+ parameters: {
107
+ docs: {
108
+ description: {
109
+ story:
110
+ "Renders two useReqraftStorage keys and captures the mount-time setter for the first one (a deliberately STALE updater). After a second key is written, writing through the stale updater must keep the second key intact — proving storage writes merge onto the latest blob rather than a closure copy. This is the regression test for page-visit tracking wiping other stored settings.",
111
+ },
112
+ },
113
+ mockData: [...storiesStorageMock],
114
+ },
115
+ render: () => {
116
+ const [someVal, setSomeVal] = useReqraftStorage<string>('some-path', 'default');
117
+ const [keyB] = useReqraftStorage<string>('key-b', 'b-default');
118
+ const [, setKeyB] = useReqraftStorage<string>('key-b', 'b-default');
119
+ // Capture the mount-time setter — it stays stale once other keys change.
120
+ const staleSetSomeVal = useRef(setSomeVal);
121
+
122
+ return (
123
+ <ReqoreControlGroup vertical>
124
+ <ReqoreP>{`some-path: ${someVal}`}</ReqoreP>
125
+ <ReqoreP>{`key-b: ${keyB}`}</ReqoreP>
126
+ <ReqoreButton onClick={() => setKeyB('b-written')}>Write B</ReqoreButton>
127
+ <ReqoreButton onClick={() => staleSetSomeVal.current('a-written')}>
128
+ Write A (stale)
129
+ </ReqoreButton>
130
+ </ReqoreControlGroup>
131
+ );
132
+ },
133
+ play: async ({ canvasElement }) => {
134
+ const canvas = within(canvasElement);
135
+
136
+ await testsWaitForText('some-path: This is a storage value');
137
+ // Write a second key through its own (fresh) updater.
138
+ await fireEvent.click(canvas.getByText('Write B'));
139
+ await testsWaitForText('key-b: b-written');
140
+ // Write the first key through the STALE mount-time updater.
141
+ await fireEvent.click(canvas.getByText('Write A (stale)'));
142
+ await testsWaitForText('some-path: a-written');
143
+ // The stale write must NOT have wiped the second key.
144
+ await testsWaitForText('key-b: b-written');
145
+ },
146
+ };
147
+
96
148
  export const ValueCanBeRemoved: Story = {
97
149
  ...StorageValue,
98
150
  parameters: {
package/src/index.tsx CHANGED
@@ -10,8 +10,6 @@ export {
10
10
  validateOptionWithRequiredGroups,
11
11
  } from './helpers/validations';
12
12
  export type { IValidationResult } from './helpers/validations';
13
- export { thinScrollbar } from './helpers/scrollbar';
14
- export * from './components/imageLightbox';
15
13
  export { ReqraftMenu } from './components/menu/Menu';
16
14
  export type {
17
15
  IReqraftMenuItem,
@@ -54,9 +52,6 @@ export type {
54
52
  ILspTextEdit,
55
53
  TLspDocumentText,
56
54
  } from './utils/lspClient.types';
57
- export * from './components/composer';
58
55
  export * from './components/smartEditor';
59
56
  export * from './components/dpqlEditor';
60
57
  export * from './components/qonsoleSmartInput';
61
- export * from './components/supportTicket';
62
- export * from './components/ticketThread';
@@ -9,6 +9,23 @@ import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
9
9
  import { currentUserStore } from '../stores/currentUser/currentUser';
10
10
  import { IReqraftProviderProps } from './ReqraftProvider';
11
11
 
12
+ /**
13
+ * Apply a single path write onto the current storage blob, seeding a fresh object
14
+ * when the user has no storage yet.
15
+ *
16
+ * `?? {}` is load-bearing: a user who has never written storage has
17
+ * `storage === undefined | null`, and `set(cloneDeep(undefined), …)` returns
18
+ * `undefined` (lodash won't set on a nullish target). Without the seed the FIRST
19
+ * write for such a user — e.g. accepting the cookie-consent banner — persists an
20
+ * empty body and silently drops the value, so the flag stays unset forever and
21
+ * the banner never closes. Exported for testing.
22
+ */
23
+ export const applyStorageWrite = (
24
+ storage: TReqraftStorage | undefined,
25
+ path: string,
26
+ value: unknown
27
+ ): TReqraftStorage => set(cloneDeep(storage) ?? {}, path, value);
28
+
12
29
  export interface IReqraftStorageProviderProps
13
30
  extends Pick<IReqraftProviderProps, 'waitForStorage'> {
14
31
  children: ReactNode;
@@ -53,26 +70,35 @@ export const ReqraftUserProvider = ({ children, waitForStorage }: IReqraftStorag
53
70
  includeAppPrefix: boolean = true
54
71
  ) {
55
72
  const _path = includeAppPrefix ? `${appName}.${path}` : path;
56
- const updatedStorage = set(cloneDeep(currentUser?.storage), _path, value);
73
+ // Base the write on the LATEST storage read straight from the store, not
74
+ // the blob captured in this callback's closure. A caller holding a stale
75
+ // updater — e.g. an imperative store subscription created on mount, before
76
+ // other keys had loaded — would otherwise persist an out-of-date blob and
77
+ // wipe every key written to storage since its closure was captured.
78
+ const latestStorage = currentUserStore.getState().currentUser?.storage;
79
+ const updatedStorage = applyStorageWrite(latestStorage, _path, value);
57
80
 
58
81
  updateCurrentUserStorage(updatedStorage);
59
82
 
60
83
  load({ body: { storage: updatedStorage } });
61
84
  },
62
- [appName, currentUser?.storage, load, updateCurrentUserStorage]
85
+ [appName, load, updateCurrentUserStorage]
63
86
  );
64
87
 
65
88
  const removeStorageValue = useCallback(
66
89
  function (path: string, includeAppPrefix: boolean = true) {
67
90
  const _path = includeAppPrefix ? `${appName}.${path}` : path;
68
91
 
69
- const updatedStorage = set(cloneDeep(currentUser?.storage), _path, null);
92
+ // Same as `updateStorage`: mutate the LATEST blob (never a stale closure
93
+ // copy), seeding a fresh object when there's no storage yet.
94
+ const latestStorage = currentUserStore.getState().currentUser?.storage;
95
+ const updatedStorage = applyStorageWrite(latestStorage, _path, null);
70
96
 
71
97
  updateCurrentUserStorage(updatedStorage);
72
98
 
73
99
  load({ body: { storage_path: _path } });
74
100
  },
75
- [appName, currentUser?.storage, load, updateCurrentUserStorage]
101
+ [appName, load, updateCurrentUserStorage]
76
102
  );
77
103
 
78
104
  const contextValue = useMemo(
@@ -1,108 +0,0 @@
1
- import { ReqoreButton, ReqoreDropdown, ReqoreTextarea } from '@qoretechnologies/reqore';
2
- import { StyledEffect } from '@qoretechnologies/reqore/dist/components/Effect';
3
- import type { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
4
- import { ComponentProps, KeyboardEvent as ReactKeyboardEvent, ReactNode } from 'react';
5
- /** Reqore's intent / effect / theme unions, without guessing import paths. */
6
- type TIntent = ComponentProps<typeof ReqoreButton>['intent'];
7
- type TEffect = ComponentProps<typeof StyledEffect>['effect'];
8
- type TCustomTheme = ComponentProps<typeof ReqoreButton>['customTheme'];
9
- /** The exact `items` shape ReqoreDropdown accepts. */
10
- type TDropdownItems = NonNullable<ComponentProps<typeof ReqoreDropdown>['items']>;
11
- /** Which editor the composer renders. */
12
- export type TComposerEditor = 'rich' | 'plain';
13
- /** One send action. With more than one, the send button splits into a primary
14
- * button (the first action) + a caret dropdown listing them all. `id` is echoed
15
- * back to `onSend` so the caller knows which action fired. */
16
- export interface IComposerSendAction {
17
- id?: string;
18
- /** button label; omit for an icon-only button (like the Qonsole send). */
19
- label?: string;
20
- icon?: IReqoreIconName;
21
- intent?: TIntent;
22
- /** per-button Reqore theme (e.g. `{ main: 'custom1' }` for the Qonsole accent). */
23
- customTheme?: TCustomTheme;
24
- /** render the button minimal (subtle, blends into the bar — the Qonsole style). */
25
- minimal?: boolean;
26
- }
27
- /** Imperative handle for consumers that drive the editor externally (e.g. the
28
- * Qonsole input inserting a completion or navigating history). */
29
- export interface IComposerHandle {
30
- focus: () => void;
31
- clear: () => void;
32
- setText: (text: string) => void;
33
- getText: () => string;
34
- }
35
- export interface IComposerProps {
36
- /** Editor flavour. `rich` = ReqoreRichTextEditor (default), `plain` = ReqoreTextarea. */
37
- editor?: TComposerEditor;
38
- /** Initial draft text (also seeds the editor for a restored draft / a story). */
39
- defaultText?: string;
40
- /** Initial staged files (a restored draft's attachments, or a demo). */
41
- defaultFiles?: File[];
42
- placeholder?: string;
43
- /**
44
- * Submit the composed message. `text` is the plain-string body; `files` are the
45
- * staged attachments (raw `File`s — the caller converts to its own upload shape);
46
- * `action` is the chosen send action's `id` (undefined for the default button).
47
- */
48
- onSend: (text: string, options: {
49
- files: File[];
50
- action?: string;
51
- }) => void | Promise<void>;
52
- /** Single-button label / icon / intent (ignored when `sendActions` is set). */
53
- sendLabel?: string;
54
- sendIcon?: IReqoreIconName;
55
- sendIntent?: TIntent;
56
- /** Default send button: minimal (subtle, Qonsole-style). Default `true`. */
57
- sendMinimal?: boolean;
58
- /** Default send button accent (Reqore `customTheme`). Default `{ main: 'custom1' }`
59
- * — the Qorus accent, which each app's theme colours (gray in a bare reqore theme). */
60
- sendCustomTheme?: TCustomTheme;
61
- /** Multiple send actions → a split send button. The first is the primary. */
62
- sendActions?: IComposerSendAction[];
63
- /** className on the split-send caret dropdown (keeps consumer test hooks, e.g.
64
- * the helpdesk `ticket-send-more`). */
65
- sendMenuClassName?: string;
66
- /** the send round-trip is in flight */
67
- sending?: boolean;
68
- /** disabled → renders a muted notice with `disabledReason` instead of the composer */
69
- disabled?: boolean;
70
- disabledReason?: string;
71
- /** submit on plain Enter (Shift/Ctrl/Alt/Meta+Enter insert a newline). Default true. */
72
- submitOnEnter?: boolean;
73
- /** clear the editor + staged files after a resolved send. Default true. */
74
- clearOnSend?: boolean;
75
- /** allow sending with empty text (e.g. attachments only). Default false. */
76
- allowEmptySend?: boolean;
77
- /** Extra attach-menu entries beyond "Upload files" (receives `addFiles`). With any
78
- * entry the attach control becomes a dropdown; otherwise a plain button. */
79
- attachMenuItems?: (addFiles: (files: File[]) => void) => TDropdownItems;
80
- /** the attach control icon (Qonsole uses `AddLine`). */
81
- attachIcon?: IReqoreIconName;
82
- /** hide the attach control entirely. */
83
- hideAttach?: boolean;
84
- /** Slots: `toolbarStart` sits after attach on the left (e.g. the Qonsole slash
85
- * dropdown), `footerActions` after it (e.g. a reference toggle), `rightActions`
86
- * left of send (e.g. the Qonsole design button), `aboveInput` on top of the
87
- * editor inside the bar (e.g. reference chips), and `belowInput` under the bar. */
88
- toolbarStart?: ReactNode;
89
- footerActions?: ReactNode;
90
- rightActions?: ReactNode;
91
- aboveInput?: ReactNode;
92
- belowInput?: ReactNode;
93
- /** Hooks for consumers layering behaviour on the editor (completions, history). */
94
- onChange?: (text: string) => void;
95
- onEditorKeyDown?: (e: ReactKeyboardEvent) => void;
96
- onFocus?: () => void;
97
- onBlur?: () => void;
98
- /** editor sizing */
99
- size?: ComponentProps<typeof ReqoreTextarea>['size'];
100
- /** drop the composer's outer margin (for tight embeds). */
101
- minimal?: boolean;
102
- /** override the bar's gradient / effect (qorus-ide passes its `custom1` accent to
103
- * match the Qonsole floating bar exactly). */
104
- effect?: TEffect;
105
- }
106
- export declare const Composer: import("react").ForwardRefExoticComponent<IComposerProps & import("react").RefAttributes<IComposerHandle>>;
107
- export {};
108
- //# sourceMappingURL=Composer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../../src/components/composer/Composer.tsx"],"names":[],"mappings":"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"}