@startsimpli/ui 0.4.26 → 0.4.27
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.27",
|
|
4
4
|
"description": "Shared UI components package for StartSimpli applications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"next": "^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
33
33
|
"react": "^18.0.0 || ^19.0.0",
|
|
34
34
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
35
|
-
"@startsimpli/auth": "0.4.
|
|
35
|
+
"@startsimpli/auth": "0.4.24"
|
|
36
36
|
},
|
|
37
37
|
"peerDependenciesMeta": {
|
|
38
38
|
"@startsimpli/auth": {
|
|
@@ -345,13 +345,15 @@ function AttachmentChip({
|
|
|
345
345
|
{name}
|
|
346
346
|
</span>
|
|
347
347
|
<span className="text-[10px] text-muted-foreground">
|
|
348
|
-
{status === '
|
|
349
|
-
?
|
|
350
|
-
: status === '
|
|
351
|
-
?
|
|
352
|
-
:
|
|
353
|
-
?
|
|
354
|
-
: null
|
|
348
|
+
{status === 'queued'
|
|
349
|
+
? 'Waiting for deck…'
|
|
350
|
+
: status === 'uploading'
|
|
351
|
+
? `Uploading… ${pct}%`
|
|
352
|
+
: status === 'error'
|
|
353
|
+
? (error || 'Upload failed')
|
|
354
|
+
: sizeBytes != null
|
|
355
|
+
? formatBytes(sizeBytes)
|
|
356
|
+
: null}
|
|
355
357
|
</span>
|
|
356
358
|
</span>
|
|
357
359
|
{status === 'error' && onRetry && (
|
|
@@ -40,7 +40,12 @@ export interface ChatMessageData {
|
|
|
40
40
|
export interface PendingAttachment {
|
|
41
41
|
id: string
|
|
42
42
|
name: string
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* `queued` = parked client-side, waiting for a document to land on (a brand-new
|
|
45
|
+
* deck has no document until the first turn creates it). It is NOT actively
|
|
46
|
+
* uploading — surfacing it as `uploading` at 0% reads as a stuck upload.
|
|
47
|
+
*/
|
|
48
|
+
status: 'queued' | 'uploading' | 'ready' | 'error'
|
|
44
49
|
/** Upload progress 0..1 while `status === 'uploading'`. */
|
|
45
50
|
progress?: number
|
|
46
51
|
/** Object/asset url for an image preview thumbnail. */
|
|
@@ -122,4 +122,50 @@ describe('useComposerUploads', () => {
|
|
|
122
122
|
])
|
|
123
123
|
)
|
|
124
124
|
})
|
|
125
|
+
|
|
126
|
+
// --- Regression: prod "dropped images never leave 0" (startsim-0fp3) ---
|
|
127
|
+
// When there is no document path (demo engine / brand-new deck whose document
|
|
128
|
+
// never arrives), a queued file used to surface as 'uploading' at progress 0
|
|
129
|
+
// indefinitely — no honest state, no way to know it's parked, no bound.
|
|
130
|
+
describe('no-document strand (startsim-0fp3)', () => {
|
|
131
|
+
it('surfaces a queued file with an honest non-uploading status, not 0%-uploading-forever', () => {
|
|
132
|
+
const uploader = fakeUploader()
|
|
133
|
+
const { result } = renderHook(() => useComposerUploads(uploader, undefined))
|
|
134
|
+
act(() => result.current.attachFiles([file('parked.png')]))
|
|
135
|
+
expect(result.current.attachments).toHaveLength(1)
|
|
136
|
+
// The user must be able to tell this is parked, not actively uploading.
|
|
137
|
+
expect(uploader.uploadAttachment).not.toHaveBeenCalled()
|
|
138
|
+
expect(result.current.attachments[0].status).toBe('queued')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('removes a queued (never-uploaded) chip and revokes its object url', () => {
|
|
142
|
+
const revoke = jest.spyOn(URL, 'revokeObjectURL')
|
|
143
|
+
const uploader = fakeUploader()
|
|
144
|
+
const { result } = renderHook(() => useComposerUploads(uploader, undefined))
|
|
145
|
+
act(() => result.current.attachFiles([file('parked.png')]))
|
|
146
|
+
const chipId = result.current.attachments[0].id
|
|
147
|
+
|
|
148
|
+
act(() => result.current.removeAttachment(chipId))
|
|
149
|
+
expect(result.current.attachments).toHaveLength(0)
|
|
150
|
+
expect(revoke).toHaveBeenCalled()
|
|
151
|
+
// Never uploaded → no server-side delete attempted.
|
|
152
|
+
expect(uploader.deleteAttachment).not.toHaveBeenCalled()
|
|
153
|
+
revoke.mockRestore()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
describe('clear() cleanup', () => {
|
|
158
|
+
it('empties all chips and revokes every object url', async () => {
|
|
159
|
+
const revoke = jest.spyOn(URL, 'revokeObjectURL')
|
|
160
|
+
const uploader = fakeUploader()
|
|
161
|
+
const { result } = renderHook(() => useComposerUploads(uploader, 'doc-1'))
|
|
162
|
+
act(() => result.current.attachFiles([file('a.png'), file('b.png')]))
|
|
163
|
+
await waitFor(() => expect(result.current.attachments).toHaveLength(2))
|
|
164
|
+
|
|
165
|
+
act(() => result.current.clear())
|
|
166
|
+
expect(result.current.attachments).toHaveLength(0)
|
|
167
|
+
expect(revoke).toHaveBeenCalledTimes(2)
|
|
168
|
+
revoke.mockRestore()
|
|
169
|
+
})
|
|
170
|
+
})
|
|
125
171
|
})
|
|
@@ -255,9 +255,10 @@ export function useComposerUploads(
|
|
|
255
255
|
items.map((it) => ({
|
|
256
256
|
id: it.id,
|
|
257
257
|
name: it.name,
|
|
258
|
-
// A queued chip
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
// A queued chip is parked (waiting for a document), NOT actively
|
|
259
|
+
// uploading — surface it honestly so it doesn't read as a stuck 0% upload.
|
|
260
|
+
status: it.status,
|
|
261
|
+
progress: it.progress,
|
|
261
262
|
thumbnailUrl: it.thumbnailUrl,
|
|
262
263
|
sizeBytes: it.sizeBytes,
|
|
263
264
|
error: it.error,
|