hlassiege/darylpublic Fork
⑂ d4ef3f2
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/daryl.git
git clone ssh://git@rickub.com/hlassiege/daryl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

fix(web): keep in-flight project changes over refreshes started before them

hlassiege committed 2026-10-13T15:24:13+02:00 Browse files
d4ef3f2 parent: c032ab9
modified web/src/project/store.ts +10 -1
@@ -22,6 +22,8 @@ export const store = reactive({
2222 const base = (pid: string) => `/api/projects/${pid}`;
2323
2424 let pending: ProjectPatch = {};
25+/** envoyé, pas encore enregistré : une relecture partie avant ne doit pas le défaire */
26+let sending: ProjectPatch = {};
2527 let flushTimer: ReturnType<typeof setTimeout> | undefined;
2628 let pollTimer: ReturnType<typeof setTimeout> | undefined;
2729
@@ -42,7 +44,7 @@ function applyPatch(p: Project, changes: ProjectPatch): void {
4244
4345 /** Remplace le projet affiché par ce que renvoie le serveur, sans perdre ce qui n'est pas encore envoyé. */
4446 export function receive(view: Project): void {
45- if (store.project && store.project.id === view.id) applyPatch(view, pending);
47+ if (store.project && store.project.id === view.id) applyPatch(view, { ...sending, ...pending });
4648 store.project = view;
4749 const i = store.list.findIndex((p) => p.id === view.id);
4850 if (i >= 0) store.list[i] = { ...store.list[i], title: view.title, status: view.status, brief: view.brief };
@@ -56,6 +58,7 @@ export async function open(pid: string): Promise<void> {
5658 try {
5759 const view = await api<Project>(base(pid));
5860 pending = {};
61+ sending = {};
5962 store.project = view;
6063 schedulePoll();
6164 // dernier projet ouvert : c'est lui que l'accueil propose de reprendre
@@ -86,10 +89,15 @@ export async function flush(): Promise<void> {
8689 if (!p || !Object.keys(pending).length) return;
8790 const changes = pending;
8891 pending = {};
92+ sending = { ...sending, ...changes };
8993 try {
9094 receive(await patchJson<Project>(base(p.id), changes));
9195 } catch (e) {
9296 toast(t("project.saveFailed", { error: (e as Error).message }), true);
97+ } finally {
98+ for (const key of Object.keys(changes) as (keyof ProjectPatch)[]) {
99+ if (sending[key] === changes[key]) delete sending[key];
100+ }
93101 }
94102 }
95103
@@ -118,6 +126,7 @@ export async function createProject(body: unknown): Promise<Project> {
118126 const view = await post<Project>("/api/projects", body);
119127 store.project = view;
120128 pending = {};
129+ sending = {};
121130 await loadList();
122131 return view;
123132 }
@@ -22,6 +22,8 @@ export const store = reactive({
22 const base = (pid: string) => `/api/projects/${pid}`;22 const base = (pid: string) => `/api/projects/${pid}`;
23 23
24 let pending: ProjectPatch = {};24 let pending: ProjectPatch = {};
25+/** envoyé, pas encore enregistré : une relecture partie avant ne doit pas le défaire */
26+let sending: ProjectPatch = {};
25 let flushTimer: ReturnType<typeof setTimeout> | undefined;27 let flushTimer: ReturnType<typeof setTimeout> | undefined;
26 let pollTimer: ReturnType<typeof setTimeout> | undefined;28 let pollTimer: ReturnType<typeof setTimeout> | undefined;
27 29
@@ -42,7 +44,7 @@ function applyPatch(p: Project, changes: ProjectPatch): void {
42 44
43 /** Remplace le projet affiché par ce que renvoie le serveur, sans perdre ce qui n'est pas encore envoyé. */45 /** Remplace le projet affiché par ce que renvoie le serveur, sans perdre ce qui n'est pas encore envoyé. */
44 export function receive(view: Project): void {46 export function receive(view: Project): void {
45- if (store.project && store.project.id === view.id) applyPatch(view, pending);47+ if (store.project && store.project.id === view.id) applyPatch(view, { ...sending, ...pending });
46 store.project = view;48 store.project = view;
47 const i = store.list.findIndex((p) => p.id === view.id);49 const i = store.list.findIndex((p) => p.id === view.id);
48 if (i >= 0) store.list[i] = { ...store.list[i], title: view.title, status: view.status, brief: view.brief };50 if (i >= 0) store.list[i] = { ...store.list[i], title: view.title, status: view.status, brief: view.brief };
@@ -56,6 +58,7 @@ export async function open(pid: string): Promise<void> {
56 try {58 try {
57 const view = await api<Project>(base(pid));59 const view = await api<Project>(base(pid));
58 pending = {};60 pending = {};
61+ sending = {};
59 store.project = view;62 store.project = view;
60 schedulePoll();63 schedulePoll();
61 // dernier projet ouvert : c'est lui que l'accueil propose de reprendre64 // dernier projet ouvert : c'est lui que l'accueil propose de reprendre
@@ -86,10 +89,15 @@ export async function flush(): Promise<void> {
86 if (!p || !Object.keys(pending).length) return;89 if (!p || !Object.keys(pending).length) return;
87 const changes = pending;90 const changes = pending;
88 pending = {};91 pending = {};
92+ sending = { ...sending, ...changes };
89 try {93 try {
90 receive(await patchJson<Project>(base(p.id), changes));94 receive(await patchJson<Project>(base(p.id), changes));
91 } catch (e) {95 } catch (e) {
92 toast(t("project.saveFailed", { error: (e as Error).message }), true);96 toast(t("project.saveFailed", { error: (e as Error).message }), true);
97+ } finally {
98+ for (const key of Object.keys(changes) as (keyof ProjectPatch)[]) {
99+ if (sending[key] === changes[key]) delete sending[key];
100+ }
93 }101 }
94 }102 }
95 103
@@ -118,6 +126,7 @@ export async function createProject(body: unknown): Promise<Project> {
118 const view = await post<Project>("/api/projects", body);126 const view = await post<Project>("/api/projects", body);
119 store.project = view;127 store.project = view;
120 pending = {};128 pending = {};
129+ sending = {};
121 await loadList();130 await loadList();
122 return view;131 return view;
123 }132 }