@imenam/mcp-design 1.0.10 → 1.0.11
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/README.md +44 -3
- package/dist/shared/feedback.d.ts +128 -0
- package/dist/shared/feedback.js +165 -0
- package/dist/shared/render.d.ts +7 -1
- package/dist/shared/render.js +32 -6
- package/dist/shared/schemas/feedback.schema.d.ts +185 -0
- package/dist/shared/schemas/feedback.schema.js +87 -0
- package/dist/shared/tree.d.ts +2 -0
- package/dist/shared/tree.js +4 -0
- package/dist/src/core/design-store.d.ts +1 -5
- package/dist/src/core/design-store.js +3 -56
- package/dist/src/core/feedback-store.d.ts +73 -0
- package/dist/src/core/feedback-store.js +308 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +61 -0
- package/dist/src/core/screenshot.d.ts +3 -0
- package/dist/src/core/screenshot.js +28 -3
- package/dist/src/gui-worker.js +103 -1
- package/dist/src/index.js +175 -1
- package/dist/src/tools/delete-design.d.ts +3 -1
- package/dist/src/tools/delete-design.js +4 -1
- package/dist/src/tools/get-feedback.d.ts +3 -0
- package/dist/src/tools/get-feedback.js +62 -0
- package/dist/src/tools/mark-feedback-processed.d.ts +8 -0
- package/dist/src/tools/mark-feedback-processed.js +29 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/index-BXpZGU8f.css +1 -0
- package/gui/dist/assets/index-CgZSm3aX.js +15 -0
- package/gui/dist/assets/{search-CSwiX0lv.js → search-DnrsSC4m.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/gui/dist/assets/index-DBZNXgF_.js +0 -10
- package/gui/dist/assets/index-PQqxUQLx.css +0 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const RemarkTargetSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
3
|
+
kind: z.ZodLiteral<"page">;
|
|
4
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
5
|
+
kind: z.ZodLiteral<"node">;
|
|
6
|
+
element: z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
name: z.ZodString;
|
|
9
|
+
type: z.ZodEnum<{
|
|
10
|
+
ellipse: "ellipse";
|
|
11
|
+
group: "group";
|
|
12
|
+
icon: "icon";
|
|
13
|
+
instance: "instance";
|
|
14
|
+
line: "line";
|
|
15
|
+
rect: "rect";
|
|
16
|
+
text: "text";
|
|
17
|
+
}>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
path: z.ZodArray<z.ZodString>;
|
|
20
|
+
box: z.ZodObject<{
|
|
21
|
+
x: z.ZodNumber;
|
|
22
|
+
y: z.ZodNumber;
|
|
23
|
+
width: z.ZodNumber;
|
|
24
|
+
height: z.ZodNumber;
|
|
25
|
+
}, z.core.$strip>;
|
|
26
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
27
|
+
kind: z.ZodLiteral<"area">;
|
|
28
|
+
box: z.ZodObject<{
|
|
29
|
+
x: z.ZodNumber;
|
|
30
|
+
y: z.ZodNumber;
|
|
31
|
+
width: z.ZodNumber;
|
|
32
|
+
height: z.ZodNumber;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
elements: z.ZodArray<z.ZodObject<{
|
|
35
|
+
id: z.ZodString;
|
|
36
|
+
name: z.ZodString;
|
|
37
|
+
type: z.ZodEnum<{
|
|
38
|
+
ellipse: "ellipse";
|
|
39
|
+
group: "group";
|
|
40
|
+
icon: "icon";
|
|
41
|
+
instance: "instance";
|
|
42
|
+
line: "line";
|
|
43
|
+
rect: "rect";
|
|
44
|
+
text: "text";
|
|
45
|
+
}>;
|
|
46
|
+
}, z.core.$strip>>;
|
|
47
|
+
}, z.core.$strip>], "kind">;
|
|
48
|
+
export declare const FeedbackFileSchema: z.ZodObject<{
|
|
49
|
+
version: z.ZodLiteral<2>;
|
|
50
|
+
remarks: z.ZodArray<z.ZodObject<{
|
|
51
|
+
id: z.ZodString;
|
|
52
|
+
designId: z.ZodString;
|
|
53
|
+
number: z.ZodNullable<z.ZodNumber>;
|
|
54
|
+
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
55
|
+
kind: z.ZodLiteral<"page">;
|
|
56
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
57
|
+
kind: z.ZodLiteral<"node">;
|
|
58
|
+
element: z.ZodObject<{
|
|
59
|
+
id: z.ZodString;
|
|
60
|
+
name: z.ZodString;
|
|
61
|
+
type: z.ZodEnum<{
|
|
62
|
+
ellipse: "ellipse";
|
|
63
|
+
group: "group";
|
|
64
|
+
icon: "icon";
|
|
65
|
+
instance: "instance";
|
|
66
|
+
line: "line";
|
|
67
|
+
rect: "rect";
|
|
68
|
+
text: "text";
|
|
69
|
+
}>;
|
|
70
|
+
}, z.core.$strip>;
|
|
71
|
+
path: z.ZodArray<z.ZodString>;
|
|
72
|
+
box: z.ZodObject<{
|
|
73
|
+
x: z.ZodNumber;
|
|
74
|
+
y: z.ZodNumber;
|
|
75
|
+
width: z.ZodNumber;
|
|
76
|
+
height: z.ZodNumber;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
79
|
+
kind: z.ZodLiteral<"area">;
|
|
80
|
+
box: z.ZodObject<{
|
|
81
|
+
x: z.ZodNumber;
|
|
82
|
+
y: z.ZodNumber;
|
|
83
|
+
width: z.ZodNumber;
|
|
84
|
+
height: z.ZodNumber;
|
|
85
|
+
}, z.core.$strip>;
|
|
86
|
+
elements: z.ZodArray<z.ZodObject<{
|
|
87
|
+
id: z.ZodString;
|
|
88
|
+
name: z.ZodString;
|
|
89
|
+
type: z.ZodEnum<{
|
|
90
|
+
ellipse: "ellipse";
|
|
91
|
+
group: "group";
|
|
92
|
+
icon: "icon";
|
|
93
|
+
instance: "instance";
|
|
94
|
+
line: "line";
|
|
95
|
+
rect: "rect";
|
|
96
|
+
text: "text";
|
|
97
|
+
}>;
|
|
98
|
+
}, z.core.$strip>>;
|
|
99
|
+
}, z.core.$strip>], "kind">;
|
|
100
|
+
text: z.ZodString;
|
|
101
|
+
capture: z.ZodNullable<z.ZodString>;
|
|
102
|
+
status: z.ZodEnum<{
|
|
103
|
+
draft: "draft";
|
|
104
|
+
sent: "sent";
|
|
105
|
+
}>;
|
|
106
|
+
round: z.ZodNullable<z.ZodNumber>;
|
|
107
|
+
createdAt: z.ZodString;
|
|
108
|
+
updatedAt: z.ZodString;
|
|
109
|
+
}, z.core.$strip>>;
|
|
110
|
+
rounds: z.ZodArray<z.ZodObject<{
|
|
111
|
+
number: z.ZodNumber;
|
|
112
|
+
title: z.ZodString;
|
|
113
|
+
submittedAt: z.ZodString;
|
|
114
|
+
remarks: z.ZodArray<z.ZodObject<{
|
|
115
|
+
id: z.ZodString;
|
|
116
|
+
designId: z.ZodString;
|
|
117
|
+
designName: z.ZodString;
|
|
118
|
+
number: z.ZodNullable<z.ZodNumber>;
|
|
119
|
+
target: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
120
|
+
kind: z.ZodLiteral<"page">;
|
|
121
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
122
|
+
kind: z.ZodLiteral<"node">;
|
|
123
|
+
element: z.ZodObject<{
|
|
124
|
+
id: z.ZodString;
|
|
125
|
+
name: z.ZodString;
|
|
126
|
+
type: z.ZodEnum<{
|
|
127
|
+
ellipse: "ellipse";
|
|
128
|
+
group: "group";
|
|
129
|
+
icon: "icon";
|
|
130
|
+
instance: "instance";
|
|
131
|
+
line: "line";
|
|
132
|
+
rect: "rect";
|
|
133
|
+
text: "text";
|
|
134
|
+
}>;
|
|
135
|
+
}, z.core.$strip>;
|
|
136
|
+
path: z.ZodArray<z.ZodString>;
|
|
137
|
+
box: z.ZodObject<{
|
|
138
|
+
x: z.ZodNumber;
|
|
139
|
+
y: z.ZodNumber;
|
|
140
|
+
width: z.ZodNumber;
|
|
141
|
+
height: z.ZodNumber;
|
|
142
|
+
}, z.core.$strip>;
|
|
143
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
144
|
+
kind: z.ZodLiteral<"area">;
|
|
145
|
+
box: z.ZodObject<{
|
|
146
|
+
x: z.ZodNumber;
|
|
147
|
+
y: z.ZodNumber;
|
|
148
|
+
width: z.ZodNumber;
|
|
149
|
+
height: z.ZodNumber;
|
|
150
|
+
}, z.core.$strip>;
|
|
151
|
+
elements: z.ZodArray<z.ZodObject<{
|
|
152
|
+
id: z.ZodString;
|
|
153
|
+
name: z.ZodString;
|
|
154
|
+
type: z.ZodEnum<{
|
|
155
|
+
ellipse: "ellipse";
|
|
156
|
+
group: "group";
|
|
157
|
+
icon: "icon";
|
|
158
|
+
instance: "instance";
|
|
159
|
+
line: "line";
|
|
160
|
+
rect: "rect";
|
|
161
|
+
text: "text";
|
|
162
|
+
}>;
|
|
163
|
+
}, z.core.$strip>>;
|
|
164
|
+
}, z.core.$strip>], "kind">;
|
|
165
|
+
text: z.ZodString;
|
|
166
|
+
capture: z.ZodNullable<z.ZodString>;
|
|
167
|
+
processed: z.ZodNullable<z.ZodObject<{
|
|
168
|
+
at: z.ZodString;
|
|
169
|
+
by: z.ZodEnum<{
|
|
170
|
+
agent: "agent";
|
|
171
|
+
user: "user";
|
|
172
|
+
}>;
|
|
173
|
+
note: z.ZodNullable<z.ZodString>;
|
|
174
|
+
}, z.core.$strip>>;
|
|
175
|
+
}, z.core.$strip>>;
|
|
176
|
+
}, z.core.$strip>>;
|
|
177
|
+
lastNumbers: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
178
|
+
}, z.core.$strip>;
|
|
179
|
+
export type FeedbackFile = z.infer<typeof FeedbackFileSchema>;
|
|
180
|
+
/**
|
|
181
|
+
* Met un fichier de version 1 au format courant : chaque retour reçoit le nom
|
|
182
|
+
* proposé par défaut, et aucune remarque n'est encore traitée. Renvoie null
|
|
183
|
+
* pour un fichier qui n'est pas en version 1.
|
|
184
|
+
*/
|
|
185
|
+
export declare function migrateFeedbackFile(raw: unknown, defaultTitle: (number: number) => string): unknown | null;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { NODE_TYPES } from "./design.schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fichier des remarques : `<dataDir>/feedback/remarks.json`.
|
|
5
|
+
*
|
|
6
|
+
* Il tient les remarques en cours (brouillons et transmises) et la liste des
|
|
7
|
+
* retours transmis, chacun figé au moment de l'envoi, hormis le suivi de son
|
|
8
|
+
* traitement. Les types TypeScript vivent dans `shared/feedback.ts`,
|
|
9
|
+
* importable par la GUI sans zod.
|
|
10
|
+
*/
|
|
11
|
+
const BoxSchema = z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() });
|
|
12
|
+
const RemarkElementSchema = z.object({ id: z.string(), name: z.string(), type: z.enum(NODE_TYPES) });
|
|
13
|
+
export const RemarkTargetSchema = z.discriminatedUnion("kind", [
|
|
14
|
+
z.object({ kind: z.literal("page") }),
|
|
15
|
+
z.object({ kind: z.literal("node"), element: RemarkElementSchema, path: z.array(z.string()), box: BoxSchema }),
|
|
16
|
+
z.object({ kind: z.literal("area"), box: BoxSchema, elements: z.array(RemarkElementSchema) }),
|
|
17
|
+
]);
|
|
18
|
+
const RemarkSchema = z.object({
|
|
19
|
+
id: z.string(),
|
|
20
|
+
designId: z.string(),
|
|
21
|
+
number: z.number().int().positive().nullable(),
|
|
22
|
+
target: RemarkTargetSchema,
|
|
23
|
+
text: z.string().min(1),
|
|
24
|
+
capture: z.string().nullable(),
|
|
25
|
+
status: z.enum(["draft", "sent"]),
|
|
26
|
+
round: z.number().int().positive().nullable(),
|
|
27
|
+
createdAt: z.string(),
|
|
28
|
+
updatedAt: z.string(),
|
|
29
|
+
});
|
|
30
|
+
const RemarkProcessingSchema = z.object({
|
|
31
|
+
at: z.string(),
|
|
32
|
+
by: z.enum(["agent", "user"]),
|
|
33
|
+
note: z.string().nullable(),
|
|
34
|
+
});
|
|
35
|
+
const RoundRemarkSchema = z.object({
|
|
36
|
+
id: z.string(),
|
|
37
|
+
designId: z.string(),
|
|
38
|
+
designName: z.string(),
|
|
39
|
+
number: z.number().int().positive().nullable(),
|
|
40
|
+
target: RemarkTargetSchema,
|
|
41
|
+
text: z.string().min(1),
|
|
42
|
+
capture: z.string().nullable(),
|
|
43
|
+
processed: RemarkProcessingSchema.nullable(),
|
|
44
|
+
});
|
|
45
|
+
const FeedbackRoundSchema = z.object({
|
|
46
|
+
number: z.number().int().positive(),
|
|
47
|
+
title: z.string().min(1),
|
|
48
|
+
submittedAt: z.string(),
|
|
49
|
+
remarks: z.array(RoundRemarkSchema),
|
|
50
|
+
});
|
|
51
|
+
export const FeedbackFileSchema = z.object({
|
|
52
|
+
version: z.literal(2),
|
|
53
|
+
remarks: z.array(RemarkSchema),
|
|
54
|
+
rounds: z.array(FeedbackRoundSchema),
|
|
55
|
+
/** Dernier numéro de pastille attribué par design : un numéro supprimé n'est jamais réutilisé. */
|
|
56
|
+
lastNumbers: z.record(z.string(), z.number().int().nonnegative()),
|
|
57
|
+
});
|
|
58
|
+
/** Version 1 : des retours sans nom, et des remarques sans suivi de traitement. */
|
|
59
|
+
const FeedbackFileV1Schema = z.object({
|
|
60
|
+
version: z.literal(1),
|
|
61
|
+
remarks: z.array(RemarkSchema),
|
|
62
|
+
rounds: z.array(z.object({
|
|
63
|
+
number: z.number().int().positive(),
|
|
64
|
+
submittedAt: z.string(),
|
|
65
|
+
remarks: z.array(RoundRemarkSchema.omit({ processed: true })),
|
|
66
|
+
})),
|
|
67
|
+
lastNumbers: z.record(z.string(), z.number().int().nonnegative()),
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Met un fichier de version 1 au format courant : chaque retour reçoit le nom
|
|
71
|
+
* proposé par défaut, et aucune remarque n'est encore traitée. Renvoie null
|
|
72
|
+
* pour un fichier qui n'est pas en version 1.
|
|
73
|
+
*/
|
|
74
|
+
export function migrateFeedbackFile(raw, defaultTitle) {
|
|
75
|
+
const v1 = FeedbackFileV1Schema.safeParse(raw);
|
|
76
|
+
if (!v1.success)
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
...v1.data,
|
|
80
|
+
version: 2,
|
|
81
|
+
rounds: v1.data.rounds.map((round) => ({
|
|
82
|
+
...round,
|
|
83
|
+
title: defaultTitle(round.number),
|
|
84
|
+
remarks: round.remarks.map((remark) => ({ ...remark, processed: null })),
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
}
|
package/dist/shared/tree.d.ts
CHANGED
|
@@ -66,6 +66,8 @@ export declare function computeLayout(nodes: RenderNode[]): Layout;
|
|
|
66
66
|
/** Origine de l'espace de coordonnées dans lequel vit un élément. */
|
|
67
67
|
export declare function spaceOriginOf(layout: Layout, parentId: string | null): Point;
|
|
68
68
|
export declare function boundsOf(layout: Layout, id: string): Box;
|
|
69
|
+
/** Vrai si les deux boîtes ont une surface commune ; un simple contact ne compte pas. */
|
|
70
|
+
export declare function boxesIntersect(a: Box, b: Box): boolean;
|
|
69
71
|
/** Boîte englobante commune à plusieurs éléments, en coordonnées document. */
|
|
70
72
|
export declare function unionBoundsOf(layout: Layout, ids: string[]): Box | null;
|
|
71
73
|
export declare const REORDER_DIRECTIONS: readonly ["front", "back", "forward", "backward"];
|
package/dist/shared/tree.js
CHANGED
|
@@ -140,6 +140,10 @@ export function spaceOriginOf(layout, parentId) {
|
|
|
140
140
|
export function boundsOf(layout, id) {
|
|
141
141
|
return layout.bounds.get(id) ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
142
142
|
}
|
|
143
|
+
/** Vrai si les deux boîtes ont une surface commune ; un simple contact ne compte pas. */
|
|
144
|
+
export function boxesIntersect(a, b) {
|
|
145
|
+
return a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
146
|
+
}
|
|
143
147
|
/** Boîte englobante commune à plusieurs éléments, en coordonnées document. */
|
|
144
148
|
export function unionBoundsOf(layout, ids) {
|
|
145
149
|
return unionBox(ids.map((id) => layout.bounds.get(id)).filter((box) => box !== undefined));
|
|
@@ -38,11 +38,7 @@ export declare class DesignStore {
|
|
|
38
38
|
private stampOf;
|
|
39
39
|
private remember;
|
|
40
40
|
private write;
|
|
41
|
-
/**
|
|
42
|
-
* Sérialise le cycle lire-modifier-écrire entre processus. `wx` échoue si le
|
|
43
|
-
* fichier existe déjà : c'est l'opération atomique qui départage deux
|
|
44
|
-
* écrivains concurrents. Non réentrant — ne jamais appeler depuis `fn`.
|
|
45
|
-
*/
|
|
41
|
+
/** Verrou inter-processus du fichier d'un design. Non réentrant — ne jamais appeler depuis `fn`. */
|
|
46
42
|
private withLock;
|
|
47
43
|
/** Identifiant lisible dérivé du nom, suffixé seulement en cas de collision. */
|
|
48
44
|
private allocateId;
|
|
@@ -2,21 +2,11 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { DesignSchema, normalizeDesignGroup, } from "../../shared/schemas/design.schema.js";
|
|
4
4
|
import { DesignError } from "./node-factory.js";
|
|
5
|
+
import { withFileLock } from "./file-lock.js";
|
|
5
6
|
import { normalizeHierarchy } from "./hierarchy.js";
|
|
6
7
|
import { migrateDesign } from "./migrate.js";
|
|
7
8
|
import { shortId, slugify } from "./ids.js";
|
|
8
9
|
import { countChanges } from "./diff.js";
|
|
9
|
-
/** Au-delà de ce délai, un verrou est réputé abandonné (processus tué en cours d'écriture). */
|
|
10
|
-
const LOCK_STALE_MS = 10_000;
|
|
11
|
-
const LOCK_RETRY_MS = 25;
|
|
12
|
-
const LOCK_TIMEOUT_MS = 5_000;
|
|
13
|
-
/**
|
|
14
|
-
* Pause bloquante. Le store est entièrement synchrone — le rendre asynchrone
|
|
15
|
-
* pour l'attente d'un verrou contaminerait tous les appelants.
|
|
16
|
-
*/
|
|
17
|
-
function sleepSync(ms) {
|
|
18
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
19
|
-
}
|
|
20
10
|
/**
|
|
21
11
|
* Persistance des designs : un fichier JSON par design dans <dataDir>/designs.
|
|
22
12
|
* Pas de base de données — un design tient dans quelques dizaines de Ko et le
|
|
@@ -75,52 +65,9 @@ export class DesignStore {
|
|
|
75
65
|
// prendrait notre propre écriture pour une modification externe.
|
|
76
66
|
this.remember(design);
|
|
77
67
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Sérialise le cycle lire-modifier-écrire entre processus. `wx` échoue si le
|
|
80
|
-
* fichier existe déjà : c'est l'opération atomique qui départage deux
|
|
81
|
-
* écrivains concurrents. Non réentrant — ne jamais appeler depuis `fn`.
|
|
82
|
-
*/
|
|
68
|
+
/** Verrou inter-processus du fichier d'un design. Non réentrant — ne jamais appeler depuis `fn`. */
|
|
83
69
|
withLock(id, fn) {
|
|
84
|
-
|
|
85
|
-
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
86
|
-
let fd;
|
|
87
|
-
for (;;) {
|
|
88
|
-
try {
|
|
89
|
-
fd = fs.openSync(lockPath, "wx");
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
if (err.code !== "EEXIST")
|
|
94
|
-
throw err;
|
|
95
|
-
try {
|
|
96
|
-
// Un processus tué laisse son verrou derrière lui : passé le délai
|
|
97
|
-
// d'obsolescence on le casse, sinon le design serait bloqué à vie.
|
|
98
|
-
if (Date.now() - fs.statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
|
|
99
|
-
fs.unlinkSync(lockPath);
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
continue; // Verrou disparu entre le open et le stat : on retente aussitôt.
|
|
105
|
-
}
|
|
106
|
-
if (Date.now() > deadline) {
|
|
107
|
-
throw new DesignError(`Design "${id}" is being written by another editor and did not free up in ${LOCK_TIMEOUT_MS} ms. Try again in a moment.`);
|
|
108
|
-
}
|
|
109
|
-
sleepSync(LOCK_RETRY_MS);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
return fn();
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
fs.closeSync(fd);
|
|
117
|
-
try {
|
|
118
|
-
fs.unlinkSync(lockPath);
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
// Verrou déjà cassé pour obsolescence par un autre processus.
|
|
122
|
-
}
|
|
123
|
-
}
|
|
70
|
+
return withFileLock(`${this.filePath(id)}.lock`, `Design "${id}"`, fn);
|
|
124
71
|
}
|
|
125
72
|
/** Identifiant lisible dérivé du nom, suffixé seulement en cas de collision. */
|
|
126
73
|
allocateId(name) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type FeedbackRound, type Remark } from "../../shared/feedback.js";
|
|
2
|
+
import { type FeedbackFile } from "../../shared/schemas/feedback.schema.js";
|
|
3
|
+
import type { Box } from "../../shared/tree.js";
|
|
4
|
+
import type { DesignStore } from "./design-store.js";
|
|
5
|
+
/** Cible demandée par la GUI ; le serveur la complète d'après le design. */
|
|
6
|
+
export type RemarkTargetInput = {
|
|
7
|
+
kind: "page";
|
|
8
|
+
} | {
|
|
9
|
+
kind: "node";
|
|
10
|
+
nodeId: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "area";
|
|
13
|
+
box: Box;
|
|
14
|
+
elementIds: string[];
|
|
15
|
+
};
|
|
16
|
+
export interface AddRemarkInput {
|
|
17
|
+
designId: string;
|
|
18
|
+
target: RemarkTargetInput;
|
|
19
|
+
text: string;
|
|
20
|
+
/** Joindre une capture de l'élément ou de la zone. Sans effet sur une remarque de page. */
|
|
21
|
+
capture: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface SetProcessedInput {
|
|
24
|
+
round: number;
|
|
25
|
+
/** Remarques visées ; toutes celles du retour quand omis. */
|
|
26
|
+
remarkIds?: string[];
|
|
27
|
+
processed: boolean;
|
|
28
|
+
by: "agent" | "user";
|
|
29
|
+
note?: string | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Remarques de l'utilisateur et retours transmis à l'agent, dans
|
|
33
|
+
* `<dataDir>/feedback/remarks.json`, captures dans `<dataDir>/feedback/captures`.
|
|
34
|
+
*
|
|
35
|
+
* Un seul fichier pour tout le projet : un retour peut couvrir plusieurs pages.
|
|
36
|
+
*/
|
|
37
|
+
export declare class FeedbackStore {
|
|
38
|
+
private readonly designs;
|
|
39
|
+
private readonly dir;
|
|
40
|
+
private readonly capturesDir;
|
|
41
|
+
private readonly file;
|
|
42
|
+
constructor(designs: DesignStore);
|
|
43
|
+
getFilePath(): string;
|
|
44
|
+
read(): FeedbackFile;
|
|
45
|
+
private mutate;
|
|
46
|
+
remarks(designId?: string): Remark[];
|
|
47
|
+
rounds(): FeedbackRound[];
|
|
48
|
+
capturePath(name: string): string;
|
|
49
|
+
readCapture(name: string): Buffer;
|
|
50
|
+
/** Complète la cible d'après l'état courant du design. */
|
|
51
|
+
private resolveTarget;
|
|
52
|
+
private renderCapture;
|
|
53
|
+
add(input: AddRemarkInput): Remark;
|
|
54
|
+
private draftOf;
|
|
55
|
+
updateText(id: string, text: string): Remark;
|
|
56
|
+
/**
|
|
57
|
+
* Retire une remarque de la liste. La capture d'un brouillon part avec lui ;
|
|
58
|
+
* celle d'une remarque transmise reste, le retour qui la contient la cite.
|
|
59
|
+
*/
|
|
60
|
+
delete(id: string): void;
|
|
61
|
+
/** Retire les remarques d'un design supprimé, selon la même règle que `delete`. */
|
|
62
|
+
deleteDesign(designId: string): void;
|
|
63
|
+
/** Fige les brouillons choisis dans un nouveau retour nommé, et les marque transmis. */
|
|
64
|
+
submit(remarkIds: string[], title: string): FeedbackRound;
|
|
65
|
+
private roundOf;
|
|
66
|
+
rename(number: number, title: string): FeedbackRound;
|
|
67
|
+
/**
|
|
68
|
+
* Déclare des remarques d'un retour traitées, ou les rouvre. Une remarque
|
|
69
|
+
* déjà traitée reste inchangée ; une nouvelle note remplace l'ancienne et
|
|
70
|
+
* son auteur, en gardant la date d'origine.
|
|
71
|
+
*/
|
|
72
|
+
setProcessed(input: SetProcessedInput): FeedbackRound;
|
|
73
|
+
}
|