@cosmicdrift/kumiko-renderer 0.160.0 → 0.162.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.162.0",
|
|
4
4
|
"description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.162.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.162.0",
|
|
20
20
|
"react": "^19.2.6",
|
|
21
21
|
"temporal-polyfill": "^0.3.2"
|
|
22
22
|
},
|
|
@@ -70,7 +70,7 @@ describe("useStreamHandler", () => {
|
|
|
70
70
|
expect(result.current.error?.code).toBe("access_denied");
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
test("abort during stream
|
|
73
|
+
test("abort during stream resets status to idle (no late done)", async () => {
|
|
74
74
|
let release!: () => void;
|
|
75
75
|
const gate = new Promise<void>((r) => {
|
|
76
76
|
release = r;
|
|
@@ -102,6 +102,6 @@ describe("useStreamHandler", () => {
|
|
|
102
102
|
release();
|
|
103
103
|
await started;
|
|
104
104
|
expect(result.current.chunks).toEqual([{ i: 0 }]);
|
|
105
|
-
expect(result.current.status).
|
|
105
|
+
expect(result.current.status).toBe("idle");
|
|
106
106
|
});
|
|
107
107
|
});
|
|
@@ -45,6 +45,8 @@ export function useStreamHandler<TChunk = unknown>(
|
|
|
45
45
|
const abort = useCallback((): void => {
|
|
46
46
|
activeCtrl.current?.abort();
|
|
47
47
|
activeCtrl.current = null;
|
|
48
|
+
// User cancel — exit streaming so UI can re-enable the start button.
|
|
49
|
+
setStatus((s) => (s === "streaming" ? "idle" : s));
|
|
48
50
|
}, []);
|
|
49
51
|
|
|
50
52
|
const reset = useCallback((): void => {
|
|
@@ -70,19 +72,31 @@ export function useStreamHandler<TChunk = unknown>(
|
|
|
70
72
|
const accumulated: TChunk[] = [];
|
|
71
73
|
for await (const chunk of dispatcher.stream<TChunk>(type, body, { signal: ctrl.signal })) {
|
|
72
74
|
// skip: a newer start() already superseded this run
|
|
73
|
-
if (ctrl.signal.aborted)
|
|
75
|
+
if (ctrl.signal.aborted) {
|
|
76
|
+
if (activeCtrl.current === null || activeCtrl.current === ctrl) setStatus("idle");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
74
79
|
accumulated.push(chunk);
|
|
75
80
|
setChunks([...accumulated]);
|
|
76
81
|
}
|
|
77
82
|
// skip: aborted after last chunk, don't mark done
|
|
78
|
-
if (ctrl.signal.aborted)
|
|
83
|
+
if (ctrl.signal.aborted) {
|
|
84
|
+
if (activeCtrl.current === null || activeCtrl.current === ctrl) setStatus("idle");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
79
87
|
setStatus("done");
|
|
80
88
|
} catch (e) {
|
|
81
89
|
// skip: abort is not an error toast
|
|
82
|
-
if (ctrl.signal.aborted)
|
|
90
|
+
if (ctrl.signal.aborted) {
|
|
91
|
+
if (activeCtrl.current === null || activeCtrl.current === ctrl) setStatus("idle");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
83
94
|
const mapped = asDispatcherError(e);
|
|
84
95
|
// skip: dispatcher-mapped abort (fetch cancelled)
|
|
85
|
-
if (mapped.code === "aborted")
|
|
96
|
+
if (mapped.code === "aborted") {
|
|
97
|
+
if (activeCtrl.current === null || activeCtrl.current === ctrl) setStatus("idle");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
86
100
|
setError(mapped);
|
|
87
101
|
setStatus("error");
|
|
88
102
|
}
|