sad-mcp 2.2.6 → 2.2.7

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.
@@ -17,6 +17,26 @@ const SEND_ONLY_PATTERNS = [
17
17
  /^התראה\s/,
18
18
  /^הפצת\s/,
19
19
  ];
20
+ // Forbidden UC archetypes — always wrong regardless of system domain.
21
+ // Each entry carries the human-readable fix hint surfaced in the error message.
22
+ const FORBIDDEN_UCS = [
23
+ {
24
+ rx: /^(login|log[\s-]?in|sign[\s-]?in|logon|log[\s-]?on|authenticate|authentication)\b/i,
25
+ hint: "Authentication is a precondition, not a UC. Add as a precondition on every UC that requires it.",
26
+ },
27
+ {
28
+ rx: /^(logout|log[\s-]?out|sign[\s-]?out|logoff|log[\s-]?off)\b/i,
29
+ hint: "Logout is a session-management detail, not a user goal. Omit it.",
30
+ },
31
+ {
32
+ rx: /^(התחברות|כניסה\s+ל|כניסה\s+אל|אימות\s+משתמש)/i,
33
+ hint: "התחברות היא תנאי מוקדם, לא מקרה שימוש. הוסף כ-precondition לכל מקרה שימוש הדורש זאת.",
34
+ },
35
+ {
36
+ rx: /^(יציאה\s+מ|יציאה\s+מה)/i,
37
+ hint: "יציאה מהמערכת אינה מקרה שימוש — השמט.",
38
+ },
39
+ ];
20
40
  // Physical verbs that describe actions outside the IS. Allowed only when
21
41
  // wrapped by a system verb (רישום / הזנה / עדכון / סימון / Record / Enter / ...).
22
42
  const PHYSICAL_VERBS = [
@@ -106,6 +126,10 @@ export function validateModel(model) {
106
126
  if (!uc.name)
107
127
  err("uc-missing-name", path, "useCase is missing a name.");
108
128
  if (uc.name && typeof uc.name === "string") {
129
+ const forbidden = FORBIDDEN_UCS.find(({ rx }) => rx.test(uc.name));
130
+ if (forbidden) {
131
+ err("forbidden-uc", `${path}.name`, `UC "${uc.name}" is a forbidden archetype. ${forbidden.hint}`);
132
+ }
109
133
  if (looksSendOnly(uc.name)) {
110
134
  err("send-only-uc", `${path}.name`, `UC name "${uc.name}" looks send-only (CR #10). Fold into the UC that decided to send.`);
111
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sad-mcp",
3
- "version": "2.2.6",
3
+ "version": "2.2.7",
4
4
  "description": "MCP server for Software Analysis and Design course materials at BGU",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,11 @@ The shared files prepended to this prompt define the layout recipes, model shape
32
32
  9. **Every system-time / cron actor association MUST carry a timing label** (e.g., "פעם ביום", "כל 5 דקות"). See §2 and §8 for rendering.
33
33
  10. **No "send-only" use cases.** If a UC's entire behavior is sending a message / notification / alert / email / SMS, it is not a UC — it is a step inside the UC that *decided* to send it. See §3.
34
34
  11. **All narrative UC documentation must be in Hebrew.** Description, preconditions, postconditions, every scenario flow-step text and extension text, user stories (`role` / `want` / `so` fields), assumptions, and open questions must all be written in Hebrew. User stories are rendered as "בתור [role], אני רוצה [want], כדי ש[so]" — write the field values in Hebrew accordingly. Identifier strings (`id`, `primaryActor: 'customer'`) stay ASCII. The validator (§12) rejects narrative fields that contain zero Hebrew characters.
35
+ 12. **Forbidden UC archetypes — never draw these, ever:**
36
+ - **Login / authentication** (`Login`, `Sign in`, `Authenticate`, `התחברות`, `כניסה למערכת`, `אימות משתמש`): Authentication is a precondition, not a user goal. Add it as a precondition on every UC that requires it; do not model it as a UC.
37
+ - **Logout** (`Logout`, `Sign out`, `יציאה מהמערכת`): Session termination is a system-infrastructure detail, not a user goal. Omit entirely.
38
+ - **Send mail / send notification** — already covered by CR #10, repeated here for emphasis. `שליחת דוא"ל`, `שליחת SMS`, `שליחת התראה`, `Send email`, `Send notification` are never UCs. These are steps inside the UC that decided to send.
39
+ The validator (§12) raises an ERROR on any of the above.
35
40
 
36
41
  ---
37
42
 
@@ -80,7 +85,7 @@ The shared files prepended to this prompt define the layout recipes, model shape
80
85
 
81
86
  **Implied UCs**: every entity the system references needs at least one UC that manages it.
82
87
 
83
- **No "send-only" use cases.** A UC must carry non-trivial behavior (decision, data entry, state change, non-trivial computation). If the only behavior is firing a message, it is not a UC — it is a system step inside the UC that *decided* to send.
88
+ **No login, logout, or send-only use cases.** Login and logout are infrastructure details — never model them as UCs; add authentication as a precondition where needed. A UC must also carry non-trivial behavior (decision, data entry, state change, non-trivial computation). If the only behavior is firing a message, it is not a UC — it is a system step inside the UC that *decided* to send.
84
89
 
85
90
  - ❌ Wrong UCs: `שליחת התראה`, `שליחת מייל אישור`, `שליחת SMS`, `הודעה למנהל`, `שליחת דוח`.
86
91
  - ✅ Fold into the decider: the alert send-step belongs inside `עדכון רמות מלאי` (which *detected* the low stock); the confirmation email belongs inside `אישור הזמנה` (which *decided* to confirm).
@@ -889,6 +894,7 @@ uml_usecase_validate_model(model_json = JSON.stringify(diagramModel))
889
894
  The validator runs locally (no API cost) and catches what the §4 and CR gates cannot enforce from prose:
890
895
  - Include target with an actor association (CR #4) → ERROR
891
896
  - Single-incoming «include» (CR #4) → ERROR
897
+ - Forbidden UC archetypes: login/authenticate, logout, send-mail/notification (CR #10, CR #12) → ERROR
892
898
  - Send-only UC names like `Print Shipping Label` / `שליחת X` (CR #10) → ERROR
893
899
  - Physical-verb UC names (`Perform X`, `ביצוע X`) not wrapped with a system verb (CR #8) → WARNING
894
900
  - Primary actor missing, unknown, or not connected as initiator (§4.1/§4.4) → ERROR
@@ -0,0 +1,944 @@
1
+ ---
2
+ name: uml-use-case-diagram
3
+ description: >
4
+ Create UML use case diagrams as interactive HTML files.
5
+ Trigger when the user asks to draw, create, or generate a use case diagram,
6
+ system use case model, actor-use case diagram, or functional requirements
7
+ diagram — including "show me the use cases", "model the system interactions",
8
+ or when extracting a use case model from a document (exam, spec, requirements).
9
+ ---
10
+
11
+ # UML Use Case Diagram Skill
12
+
13
+ > **שפת תקשורת:** כל ההסברים, השאלות, והתגובות בשיחה יהיו בעברית. קבצי HTML נוצרים עם תוויות בעברית. English is used only inside code comments and technical identifiers.
14
+
15
+ > **Canonical authority:** The notation rules in `NOTATION.md` (sibling file) are the ground truth. If any rule below conflicts with `NOTATION.md`, `NOTATION.md` wins.
16
+
17
+ Produce a single self-contained interactive HTML file with an embedded SVG use case diagram.
18
+ The shared files prepended to this prompt define the layout recipes, model shape, and export button you must use.
19
+
20
+ ---
21
+
22
+ ## CRITICAL RULES — Check Before Every Output
23
+
24
+ 1. **Hebrew: male form only.** אח (not אחות), מזכיר (not מזכירה), מטופל (not מטופלת), רופא (not רופאה), לקוח (not לקוחה), מנהל (not מנהלת), סטודנט (not סטודנטית). Verify every Hebrew label before writing.
25
+ 2. **No generic catch-all use cases.** "ניהול הגדרות מערכת" is forbidden. Each entity → its own UC.
26
+ 3. **All ellipses have solid borders.** Dashed = relationship lines only, never ellipse borders.
27
+ 4. **«include» requires BOTH: ≥2 base UCs point to it AND zero actor associations on the target.** Fail either half → the «include» is wrong. A single incoming «include» is always wrong — fold into the parent. See §4 for the mandatory justification gate.
28
+ 5. **Every use case must have at least one association line to an actor.**
29
+ 6. **Actors must not overlap** — minimum 200px vertical distance between actor centers.
30
+ 7. **Default: zero «include» and zero «extend».** Most diagrams need none. More than two is almost always wrong. §4 defines a gate every such relationship must pass before you draw it.
31
+ 8. **UC labels describe actions WITH the information system, not physical real-world actions.** See §3 for the full rule and verb list.
32
+ 9. **Every system-time / cron actor association MUST carry a timing label** (e.g., "פעם ביום", "כל 5 דקות"). See §2 and §8 for rendering.
33
+ 10. **No "send-only" use cases.** If a UC's entire behavior is sending a message / notification / alert / email / SMS, it is not a UC — it is a step inside the UC that *decided* to send it. See §3.
34
+ 11. **All narrative UC documentation must be in Hebrew.** Description, preconditions, postconditions, every scenario flow-step text and extension text, user stories (`role` / `want` / `so` fields), assumptions, and open questions must all be written in Hebrew. User stories are rendered as "בתור [role], אני רוצה [want], כדי ש[so]" — write the field values in Hebrew accordingly. Identifier strings (`id`, `primaryActor: 'customer'`) stay ASCII. The validator (§12) rejects narrative fields that contain zero Hebrew characters.
35
+ 12. **Forbidden UC archetypes — never draw these, ever:**
36
+ - **Login / authentication** (`Login`, `Sign in`, `Authenticate`, `התחברות`, `כניסה למערכת`, `אימות משתמש`): Authentication is a precondition, not a user goal. Add it as a precondition on every UC that requires it; do not model it as a UC.
37
+ - **Logout** (`Logout`, `Sign out`, `יציאה מהמערכת`): Session termination is a system-infrastructure detail, not a user goal. Omit entirely.
38
+ - **Send mail / send notification** — already covered by CR #10, repeated here for emphasis. `שליחת דוא"ל`, `שליחת SMS`, `שליחת התראה`, `Send email`, `Send notification` are never UCs. These are steps inside the UC that decided to send.
39
+ The validator (§12) raises an ERROR on any of the above.
40
+
41
+ ---
42
+
43
+ ## 1. Gather Requirements
44
+
45
+ | Item | Notes |
46
+ |---|---|
47
+ | **System name/boundary** | Names the rectangle |
48
+ | **Human actors** (roles) | External humans who interact with the system |
49
+ | **System actors** (external systems) | Drawn as a stick figure with `«system»` stereotype (not a rectangle) |
50
+ | **Actor generalizations** | Child actor does everything parent does + more |
51
+ | **System administrator** | Almost always present — manages master data for every entity |
52
+ | **Use cases** | One per user goal at goal-granularity level |
53
+ | **Shared mandatory sub-behaviors** | «include» candidates — must pass the §4 gate |
54
+ | **Optional/conditional behaviors** | «extend» candidates — must pass the §4 gate |
55
+ | **Language for labels** | Hebrew, English, or bilingual |
56
+
57
+ ---
58
+
59
+ ## 2. Actor Rules
60
+
61
+ - **Human actor**: stick figure with name below.
62
+ - **System actor**: stick figure with `«system»` (italic, 9px) on the first label line and the actor name on the second. Use a distinct head fill (`#fef3c7` amber) to visually distinguish from humans. **Do NOT draw a rectangle** — the course does not teach the rectangle form.
63
+ - **Positioning**: primary actors on the left, secondary/admin on the right, external systems that connect to only one UC below the boundary.
64
+ - **Generalization**: solid line, hollow triangle at the parent end. Route as an **L-shape** (horizontal away from boundary, then vertical, then back) — never a diagonal that crosses other actors.
65
+
66
+ **System administrator** — include unless explicitly excluded. Responsible for managing the master data of every entity the system references. Each entity gets its own specific UC (not one catch-all).
67
+
68
+ **System-time / cron actor (שחקן זמן).** Draw as a system-style actor (amber head, `«system»` label). Every association line from a time actor MUST carry a visible timing label at the midpoint (e.g., `פעם ביום`, `כל 5 דקות`, `בסוף חודש`). Without a timing label the trigger frequency is ambiguous and the diagram is incomplete. Rendering: the label is injected as a `<g class="timing-label">` (white `<rect>` + `<text>`) after `fixLayout` sets the endpoint coordinates — see §8.
69
+
70
+ ---
71
+
72
+ ## 3. Use Case Rules
73
+
74
+ **Granularity**: one complete user goal per UC, achievable in one session.
75
+
76
+ **Labels describe actions WITH the information system, not physical real-world actions.** A use case is the actor's interaction with the IS; the verb must imply data entry, query, or a system-state change.
77
+
78
+ - ✅ Preferred verbs: `רישום`, `הזנה`, `עדכון`, `הצגה`, `הפקה` (of a report), `שליחה` (of a notification), `אישור`, `הקצאה`, `חיפוש`, `סגירה`, `פתיחה` (of a record).
79
+ - ❌ Bare physical verbs that describe things happening outside the system: `הכנת משלוח` (physical preparing), `ספירת מלאי` (physical counting), `מסירת חבילה` (physical delivering), `תשלום במזומן` (physical paying).
80
+ - **Fix rule:** when the requirement uses a physical verb, wrap it with a system verb. `הכנת משלוח` → `רישום הכנת משלוח` or `סימון משלוח כמוכן`. `ביצוע ספירת מלאי` → `הזנת תוצאות ספירת מלאי`. `קבלת משלוח` → `קליטת משלוח נכנס` (data entry on arrival).
81
+
82
+ **When to combine vs. separate "ניהול X":**
83
+ - Simple entity (just a form): single "ניהול X" is fine.
84
+ - Complex entity with sub-items, lifecycle states, or different actors per operation: separate UCs (open / update / cancel / approve).
85
+
86
+ **Implied UCs**: every entity the system references needs at least one UC that manages it.
87
+
88
+ **No "send-only" use cases.** A UC must carry non-trivial behavior (decision, data entry, state change, non-trivial computation). If the only behavior is firing a message, it is not a UC — it is a system step inside the UC that *decided* to send.
89
+
90
+ - ❌ Wrong UCs: `שליחת התראה`, `שליחת מייל אישור`, `שליחת SMS`, `הודעה למנהל`, `שליחת דוח`.
91
+ - ✅ Fold into the decider: the alert send-step belongs inside `עדכון רמות מלאי` (which *detected* the low stock); the confirmation email belongs inside `אישור הזמנה` (which *decided* to confirm).
92
+ - This rule applies even when the send is triggered by a condition — conditional sending is part of the conditional UC, not its own UC.
93
+
94
+ ---
95
+
96
+ ## 4. Relationship Gates — Mandatory Justifications
97
+
98
+ Every relationship in the diagram — actor-UC associations, «include», and «extend» — must pass a written justification block BEFORE it is drawn. **If you cannot fill the block in truthfully, the relationship does not exist.** Write these blocks in your planning text (not in the final HTML).
99
+
100
+ ### 4.1 Association justification (actor ↔ UC)
101
+
102
+ ```
103
+ Association: [actor id] ↔ [UC id]
104
+ Actor role: initiator / participant / recipient / supplier
105
+ Does this actor TRIGGER / INITIATE the UC? yes / no
106
+ One-line justification: "The actor ___ in order to ___"
107
+ Description shown on click (Hebrew): ___________________
108
+
109
+ → If the actor has no meaningful interaction with the UC → DELETE the association.
110
+ → Every UC MUST have ≥1 association where "initiates = yes". A UC with no initiator is orphaned — fix it or delete the UC.
111
+ → A human actor connected only as "recipient" of a notification is usually wrong — the initiator of the notification-sending UC is who you want (see CRITICAL RULE #10, no send-only UCs).
112
+ ```
113
+
114
+ The "Description shown on click" is the text surfaced in the HTML by the `showAssocDesc` popup — see §8. Store it on the connection object under `description`.
115
+
116
+ ### 4.2 «include» justification
117
+
118
+ **Default to zero.** Treat every «include» as guilty until proven innocent.
119
+
120
+ ```
121
+ «include» candidate: __________________________________
122
+ Base UC #1 (actor-initiated, runs target every time): __________
123
+ Base UC #2 (actor-initiated, runs target every time): __________
124
+ Target has zero direct actor associations? yes / no
125
+ Extracting reduces real duplication (not just "feels like a step")? yes / no
126
+
127
+ → If Base UC #2 is blank → DELETE the «include». Fold into Base UC #1.
128
+ → If actor-associations = yes → DELETE. The target is a regular UC, not a helper.
129
+ → If duplication reduction = no → DELETE. A step inside one UC is just a step.
130
+ ```
131
+
132
+ Semantics: «include» means the target runs **every single time** the base runs, unconditionally. It is never "sometimes X". That is «extend».
133
+
134
+ Arrow: dashed, open arrowhead, `Base UC → Included UC`.
135
+
136
+ ### 4.3 «extend» justification
137
+
138
+ ```
139
+ «extend» candidate: __________________________________
140
+ Base UC being extended: __________
141
+ Exact trigger: "when __________ happens" (one sentence, specific condition)
142
+ Base UC runs correctly without the extension? yes / no
143
+ Is this behavior significant enough to model (not every trivial optional path)? yes / no
144
+
145
+ → If trigger is vague or cannot be stated in one sentence → DELETE.
146
+ → If base UC does NOT run correctly without it → it is not «extend». Reconsider as «include» (if always) or as part of the base UC.
147
+ → If insignificant → DELETE. Not every optional step deserves «extend».
148
+ ```
149
+
150
+ Arrow: dashed, open arrowhead, `Extension UC → Base UC`. Use at most once or twice per diagram.
151
+
152
+ ### 4.4 UC documentation block (mandatory)
153
+
154
+ **Every UC on the diagram also carries full Visual Paradigm-style documentation.** This is not justification text — it is the data that populates `useCaseDocs[id]` (see §9) and is rendered inside the per-UC modal (see §8). Write one block per UC in your planning before emitting the HTML.
155
+
156
+ ```
157
+ UC documentation: [uc-id]
158
+ name: _______
159
+ primaryActor: _______ (must be an actor connected to this UC with role: 'initiator' per §4.1)
160
+ secondaryActors: [...] (actors with role: 'participant' / 'recipient' / 'supplier')
161
+ description: one-sentence summary of what the UC does
162
+
163
+ preconditions:
164
+ - ___________________
165
+ - ___________________
166
+ postconditions:
167
+ - ___________________
168
+
169
+ scenarios:
170
+ - id: sc-happy, name: "מסלול מרכזי"
171
+ flow:
172
+ Write 5–10 concrete steps. Actor steps name specific data entered or a specific
173
+ action taken — never "ממלא טופס" or "שולח בקשה" alone. System steps name the
174
+ specific validation performed, state change made, or feedback shown — never
175
+ "מעבד את הבקשה" alone. Each step must be one observable action; split
176
+ compound actions into separate steps.
177
+ Examples of sufficient granularity:
178
+ [actor] מזין מספר סטודנט, שם פרטי, שם משפחה וכתובת דוא"ל
179
+ [system] מוודא כי מספר הסטודנט אינו קיים במערכת
180
+ [system] שומר את הרשומה עם סטטוס "ממתין לאישור" ומציג הודעת אישור
181
+ 1. [actor:primaryActor] ___________________
182
+ 2. [system] ___________________
183
+ 3. [include:uc-xxx] ___________________ (only if §4.2 gate passed)
184
+ 4. [actor:secondary] ___________________
185
+ extensions: (error/exception handling — one per step where failure is realistic)
186
+ 2a. אם ___________ — המערכת ___________ (e.g. wrong input, not found, timeout)
187
+ 4a. אם ___________ — המערכת ___________
188
+
189
+ Additional scenarios — add one when the SAME GOAL is achieved via a meaningfully different PATH or TRIGGER.
190
+ Rule: same "what", different "how" or different starting condition → new scenario.
191
+ Examples: "רישום רגיל" vs "רישום מאוחר עם קנס"; "תשלום בכרטיס" vs "תשלום בקופון".
192
+ Do NOT add a scenario just for a different error outcome — that belongs in extensions of the main scenario.
193
+
194
+ - id: sc-alt-1, name: "מסלול חלופי: ___"
195
+ flow: ...
196
+ extensions: ...
197
+
198
+ userStories: (as many as the UC warrants — one per distinct stakeholder need; not a fixed number)
199
+ - בתור [תפקיד], אני רוצה [מה], כדי ש[למה].
200
+ - בתור [תפקיד שונה או אותו תפקיד עם מטרה שונה], אני רוצה [...], כדי ש[...].
201
+ - ... (keep adding until every user-facing goal tied to this UC is represented)
202
+
203
+ wireframe?: (optional — only where the UC benefits from a UI mock; see §WF)
204
+ ```
205
+
206
+ **Gates enforced before publishing:**
207
+ - Exactly one `primaryActor`; the actor exists in `actors[]` AND an association exists with `role: 'initiator'` on this UC (§4.1).
208
+ - At least one scenario with at least one flow step.
209
+ - Every `[actor:id]` in a flow step references a real actor id.
210
+ - Every `[include:id]` in a flow step references a real UC id AND corresponds to an `«include»` relationship that passed §4.2.
211
+ - Every `Na` extension references a valid step number `N` in the same scenario's flow.
212
+ - As many user stories as the UC warrants (minimum one) — cover every distinct user goal / role / scenario that motivates the UC. Do not stop at one.
213
+
214
+ ### Quick disqualifiers
215
+
216
+ - Exactly one incoming «include» arrow → fold into the parent.
217
+ - Actor directly connects to an alleged «include» target → it is a standalone UC, not a sub-behavior.
218
+ - "Always happens as part of X" alone is not enough — it must also be shared by ≥2 bases.
219
+ - Conditional or optional behavior → this is «extend» territory, not «include».
220
+
221
+ ---
222
+
223
+ ## 5. Notation
224
+
225
+ Colors, stroke widths, and font sizes live in §10 Styling. Line-type semantics and canonical arrow directions are owned by `NOTATION.md §3`. This section captures only the implementation-specific details the generator needs.
226
+
227
+ ### Actor rendering
228
+ - Human: stick figure — head circle + body/arms/legs lines, name below.
229
+ - System: same stick figure with amber head fill and `«system»` label line above the name.
230
+ - Generalization: solid line, hollow triangle at parent end, L-shaped routing.
231
+
232
+ ### System boundary
233
+ Large rounded rectangle (`rx=12`), title bar on top with system name (white text on `#1e40af` background).
234
+
235
+ ### Use case ellipses
236
+ - Horizontal ellipse: `rx` 70–125, `ry` 25–32 (size to fit text).
237
+ - Fill `#d4e9f7`, solid border `#2980b9` (1.8px) — **always solid, no exceptions**.
238
+ - Text centered, 11px, may wrap to 2 lines.
239
+
240
+ ### Ellipse endpoint calculation (mandatory)
241
+ Never draw a line to the ellipse center — it disappears under the fill. Compute the boundary intersection:
242
+
243
+ ```javascript
244
+ // Shared layout-recipes.md: ellipse-boundary-intersect(cx, cy, rx, ry, externalPoint)
245
+ angle = Math.atan2(externalPoint.y - cy, externalPoint.x - cx);
246
+ endpoint = { x: cx + rx * Math.cos(angle), y: cy + ry * Math.sin(angle) };
247
+ ```
248
+
249
+ ### SVG draw order
250
+ 1. Background + dot grid
251
+ 2. System boundary rectangle
252
+ 3. Association lines (solid)
253
+ 4. «include» / «extend» dashed lines with labels
254
+ 5. Generalization lines with triangles
255
+ 6. Use case ellipses (covers line endpoints — draw last among shapes)
256
+ 7. Actor figures and labels
257
+
258
+ **No legend.** Do not add a legend bar to the diagram.
259
+
260
+ ### «include» / «extend» arrow template
261
+
262
+ Define markers once in `<defs>` using `<polyline>` (NOT `<polygon>` — polygon closes the triangle, producing a double-headed appearance):
263
+
264
+ ```svg
265
+ <defs>
266
+ <marker id="arrow-include" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
267
+ <polyline points="0 0, 9 3.5, 0 7" fill="none" stroke="#7c3aed" stroke-width="1.2"/>
268
+ </marker>
269
+ <marker id="arrow-extend" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
270
+ <polyline points="0 0, 9 3.5, 0 7" fill="none" stroke="#7c3aed" stroke-width="1.2"/>
271
+ </marker>
272
+ </defs>
273
+ ```
274
+
275
+ For each «include»/«extend» line, add a `<text>` with `data-label-for="{from-id} {to-id}"` — `fixLayout` reads this and repositions the label to the line midpoint automatically:
276
+
277
+ ```svg
278
+ <line data-conn-from="uc-a" data-conn-to="uc-b"
279
+ x1="0" y1="0" x2="0" y2="0"
280
+ stroke="#7c3aed" stroke-width="1.6" stroke-dasharray="6 4"
281
+ marker-end="url(#arrow-include)"/>
282
+ <text data-label-for="uc-a uc-b" x="0" y="0"
283
+ text-anchor="middle" font-size="9.5" fill="#7c3aed"
284
+ font-family="'IBM Plex Mono',monospace">«include»</text>
285
+ ```
286
+
287
+ **Never write `«include target»` or any annotation inside a UC ellipse group.**
288
+
289
+ ---
290
+
291
+ ## 6. Layout
292
+
293
+ One entry point: `fixLayout(layout)`. It runs on `DOMContentLoaded` **and** whenever the Fix Layout button is clicked. It is idempotent — clicking 1 or 10 times produces the same result. Initial `cx`/`cy` coordinates you write in the SVG are starting hints; `fixLayout` is the authority.
294
+
295
+ ### Data structure
296
+
297
+ ```javascript
298
+ const diagramLayout = {
299
+ svgId: 'diagram-svg',
300
+ boundary: { x: 180, y: 60, width: 640, height: 700 },
301
+ actors: [
302
+ { id: 'a1', label: 'מטופל', side: 'left', x: 80, y: 300, ucIds: ['uc1','uc2','uc3'] },
303
+ { id: 'a2', label: 'מנהל', side: 'right', x: 900, y: 350, ucIds: ['uc4','uc5'] }
304
+ ],
305
+ useCases: [
306
+ { id: 'uc1', cx: 320, cy: 200, rx: 90, ry: 28 },
307
+ { id: 'uc2', cx: 320, cy: 300, rx: 90, ry: 28 }
308
+ // ... all UCs
309
+ ],
310
+ connections: [
311
+ { from: 'a1', to: 'uc1', type: 'association' },
312
+ { from: 'uc1', to: 'uc6', type: 'include' }
313
+ // ... all connections
314
+ ]
315
+ };
316
+ ```
317
+
318
+ ### SVG element conventions (required)
319
+
320
+ - UC groups: `<g id="uc-group-{ucId}">` with `<ellipse cx="..." cy="...">` — do NOT use `transform` to position UCs.
321
+ - Actor groups: `<g id="actor-group-{actorId}" transform="translate(x,y)">`.
322
+ - Connection lines: `<line data-conn-from="{id}" data-conn-to="{id}" x1 y1 x2 y2>`.
323
+
324
+ ### fixLayout implementation
325
+
326
+ `fixLayout`, its constants, and all other runtime functions are provided by
327
+ `https://themathbible.com/sad-mcp/uc-diagram-common.js` — **do not inline them**.
328
+ The HTML `<script>` block must contain only the diagram-specific data objects
329
+ (`diagramLayouts`, `actorDescriptions`, `assocDescriptions`, `useCaseDocs`, `diagramModel`).
330
+
331
+ The function signature and constants for reference (implementation is external):
332
+
333
+ ```javascript
334
+ const HEADER_H = 44; // boundary header height
335
+ const UC_PAD = 30; // min gap from boundary edge to nearest UC edge
336
+ const UC_GAP = 14; // min gap between UC ellipses in the same column
337
+ const ACTOR_PAD = 80; // horizontal gap between boundary and actor column
338
+
339
+ function fixLayout(layout) {
340
+ const svg = document.getElementById(layout.svgId);
341
+ if (!svg) return;
342
+ const b = layout.boundary;
343
+
344
+ // Step 1 — Group UCs by the side of their owning actor.
345
+ const ucSide = {};
346
+ layout.actors.forEach(a =>
347
+ a.ucIds.forEach(uid => {
348
+ ucSide[uid] = ucSide[uid] && ucSide[uid] !== a.side ? 'center' : a.side;
349
+ })
350
+ );
351
+ const cols = { left: [], right: [], center: [] };
352
+ layout.useCases.forEach(uc => cols[ucSide[uc.id] || 'center'].push(uc));
353
+
354
+ // Step 2 — Sort each column by actor order (top-to-bottom on that side).
355
+ function sortCol(ucs, side) {
356
+ const ordered = layout.actors.filter(a => a.side === side).sort((a, bb) => a.y - bb.y);
357
+ ucs.sort((a, bb) => {
358
+ const ai = ordered.findIndex(act => act.ucIds.includes(a.id));
359
+ const bi = ordered.findIndex(act => act.ucIds.includes(bb.id));
360
+ return (ai < 0 ? 999 : ai) - (bi < 0 ? 999 : bi);
361
+ });
362
+ }
363
+ sortCol(cols.left, 'left');
364
+ sortCol(cols.right, 'right');
365
+
366
+ // Step 3 — Adaptive column X positions. Account for actual ellipse widths so
367
+ // adjacent columns never overlap horizontally. Widen the boundary if needed.
368
+ const COL_GAP = 30;
369
+ const maxRx = ucs => (ucs.length ? Math.max(...ucs.map(u => u.rx)) : 0);
370
+ const lRx = maxRx(cols.left), cRx = maxRx(cols.center), rRx = maxRx(cols.right);
371
+ const gaps =
372
+ (cRx ? 2 * COL_GAP : (lRx && rRx ? 2 * COL_GAP : 0));
373
+ const needed = 2 * UC_PAD + 2 * lRx + 2 * cRx + 2 * rRx + gaps;
374
+ if (b.width < needed) b.width = needed;
375
+
376
+ const leftX = b.x + UC_PAD + lRx;
377
+ const rightX = b.x + b.width - UC_PAD - rRx;
378
+ const centerX = (leftX + rightX) / 2;
379
+
380
+ function place(ucs, colX, colName) {
381
+ ucs.forEach((uc, i) => {
382
+ uc.cy = b.y + HEADER_H + UC_PAD + uc.ry + i * (uc.ry * 2 + UC_GAP);
383
+ uc.cx = colX;
384
+ uc.col = colName;
385
+ });
386
+ }
387
+ place(cols.left, leftX, 'left');
388
+ place(cols.right, rightX, 'right');
389
+ place(cols.center, centerX, 'center');
390
+
391
+ // Step 4 — Clamp and resolve overlaps. Idempotent fixed-point loop.
392
+ const minX = b.x + UC_PAD;
393
+ const maxX = b.x + b.width - UC_PAD;
394
+ const minY = b.y + HEADER_H + UC_PAD;
395
+ let changed = true, iter = 0;
396
+ while (changed && iter++ < 40) {
397
+ changed = false;
398
+ for (const uc of layout.useCases) {
399
+ const nx = Math.max(minX + uc.rx, Math.min(maxX - uc.rx, uc.cx));
400
+ if (nx !== uc.cx) { uc.cx = nx; changed = true; }
401
+ const ny = Math.max(minY + uc.ry, uc.cy);
402
+ if (ny !== uc.cy) { uc.cy = ny; changed = true; }
403
+ }
404
+ for (const col of Object.values(cols)) {
405
+ col.sort((a, bb) => a.cy - bb.cy);
406
+ for (let i = 1; i < col.length; i++) {
407
+ const prev = col[i - 1], cur = col[i];
408
+ const needed = prev.cy + prev.ry + UC_GAP + cur.ry;
409
+ if (cur.cy < needed) { cur.cy = needed; changed = true; }
410
+ }
411
+ }
412
+ }
413
+
414
+ // Step 5 — Expand boundary height to fit all UCs.
415
+ const maxBottom = Math.max(...layout.useCases.map(uc => uc.cy + uc.ry));
416
+ b.height = Math.max(b.height, maxBottom - b.y + UC_PAD);
417
+
418
+ // Step 6 — Re-center actors on their UC group; push bottom actors below boundary.
419
+ layout.actors.forEach(actor => {
420
+ const myUCs = layout.useCases.filter(u => actor.ucIds.includes(u.id));
421
+ if (actor.side === 'left' || actor.side === 'right') {
422
+ if (myUCs.length) actor.y = myUCs.reduce((s, u) => s + u.cy, 0) / myUCs.length;
423
+ actor.x = actor.side === 'left' ? b.x - ACTOR_PAD : b.x + b.width + ACTOR_PAD;
424
+ } else if (actor.side === 'bottom') {
425
+ actor.y = b.y + b.height + ACTOR_PAD;
426
+ }
427
+ });
428
+
429
+ // Step 7 — Write UC positions to the SVG (absolute attributes; translate deltas on text).
430
+ layout.useCases.forEach(uc => {
431
+ const g = document.getElementById('uc-group-' + uc.id);
432
+ if (!g) return;
433
+ const el = g.querySelector('ellipse');
434
+ if (!el) return;
435
+ const dx = uc.cx - parseFloat(el.getAttribute('cx'));
436
+ const dy = uc.cy - parseFloat(el.getAttribute('cy'));
437
+ el.setAttribute('cx', uc.cx);
438
+ el.setAttribute('cy', uc.cy);
439
+ g.querySelectorAll('text').forEach(t => {
440
+ t.setAttribute('x', parseFloat(t.getAttribute('x') || 0) + dx);
441
+ t.setAttribute('y', parseFloat(t.getAttribute('y') || 0) + dy);
442
+ });
443
+ g.removeAttribute('transform');
444
+ });
445
+ layout.actors.forEach(actor => {
446
+ const g = document.getElementById('actor-group-' + actor.id);
447
+ if (g) g.setAttribute('transform', `translate(${actor.x},${actor.y})`);
448
+ });
449
+
450
+ // Step 8 — Recalculate every connection line endpoint.
451
+ svg.querySelectorAll('[data-conn-from][data-conn-to]').forEach(line => {
452
+ const fId = line.getAttribute('data-conn-from');
453
+ const tId = line.getAttribute('data-conn-to');
454
+ const fUC = layout.useCases.find(u => u.id === fId);
455
+ const fAc = layout.actors.find(a => a.id === fId);
456
+ const tUC = layout.useCases.find(u => u.id === tId);
457
+ const tAc = layout.actors.find(a => a.id === tId);
458
+ const from = fAc ? { x: fAc.x, y: fAc.y } : fUC ? { x: fUC.cx, y: fUC.cy } : null;
459
+ const to = tAc ? { x: tAc.x, y: tAc.y } : tUC ? { x: tUC.cx, y: tUC.cy } : null;
460
+ if (!from || !to) return;
461
+ let x1 = from.x, y1 = from.y, x2 = to.x, y2 = to.y;
462
+ if (fUC) { const a = Math.atan2(to.y - fUC.cy, to.x - fUC.cx); x1 = fUC.cx + fUC.rx * Math.cos(a); y1 = fUC.cy + fUC.ry * Math.sin(a); }
463
+ if (tUC) { const a = Math.atan2(from.y - tUC.cy, from.x - tUC.cx); x2 = tUC.cx + tUC.rx * Math.cos(a); y2 = tUC.cy + tUC.ry * Math.sin(a); }
464
+ line.setAttribute('x1', x1); line.setAttribute('y1', y1);
465
+ line.setAttribute('x2', x2); line.setAttribute('y2', y2);
466
+ });
467
+
468
+ // Step 9 — Resize boundary rect and SVG viewBox (BOTH width and height).
469
+ const br = svg.querySelector('.system-boundary, rect[rx="12"]');
470
+ if (br) { br.setAttribute('width', b.width); br.setAttribute('height', b.height); }
471
+ const vb = svg.getAttribute('viewBox');
472
+ if (vb) {
473
+ const p = vb.split(/\s+/);
474
+ // Height: fit boundary + any bottom actors.
475
+ p[3] = Math.max(parseFloat(p[3]), b.y + b.height + 120);
476
+ // Width: fit the rightmost actor + label margin. Without this, widening the
477
+ // boundary pushes right-column actors off the visible SVG.
478
+ const ACTOR_LABEL_MARGIN = 120; // covers stick figure width + longest label
479
+ const maxActorX = layout.actors.length
480
+ ? Math.max(...layout.actors.map(a => a.x))
481
+ : b.x + b.width;
482
+ p[2] = Math.max(parseFloat(p[2]), maxActorX + ACTOR_LABEL_MARGIN);
483
+ svg.setAttribute('viewBox', p.join(' '));
484
+ }
485
+
486
+ // Step 10 — Decorate timed associations (see §8 renderTimingLabels).
487
+ if (typeof renderTimingLabels === 'function') renderTimingLabels(layout);
488
+ }
489
+ ```
490
+
491
+ ### Fix Layout button
492
+
493
+ ```html
494
+ <div style="text-align:center; margin:8px 0;">
495
+ <button onclick="fixLayout(diagramLayout)" class="fix-btn">Fix Layout</button>
496
+ </div>
497
+ ```
498
+
499
+ ---
500
+
501
+ ## 7. Splitting Into Multiple Diagrams
502
+
503
+ Split the use case diagram into multiple tabs in one HTML file when **either** trigger fires:
504
+
505
+ 1. **Total UC count > 8.**
506
+ 2. **≥2 actor groups each own ≥3 UCs, and those groups share no UCs with each other** (truly disjoint responsibilities — e.g., clinical side vs. admin side).
507
+
508
+ If neither trigger fires, produce a single tab. Do not split on gut feeling.
509
+
510
+ ### Examples
511
+
512
+ | Scenario | UC count | Actor groups | Split? | Reason |
513
+ |---|---|---|---|---|
514
+ | Small library system | 5 | 1 (reader) | **No** | Both triggers fail |
515
+ | Coffee shop | 7 | 2 (customer 4 UCs, barista 3 UCs) disjoint | **Yes** | Trigger #2 fires |
516
+ | Hospital clinic | 12 | 1 (doctor) owns most | **Yes** | Trigger #1 fires |
517
+ | Course registration | 8 | 2 (student 5, admin 3) — share "login" only | **Yes** | Trigger #2 fires (login handled via cross-tab stub) |
518
+
519
+ ### Mechanics
520
+
521
+ - One tab per actor group or per functional area.
522
+ - **Cross-tab connections:** draw the stub on each side with the partner's name and a `(→ Tab N)` label. Do NOT try to route a line across tabs.
523
+ - Each tab has its own complete SVG (its own boundary, actors, ellipses, lines).
524
+ - Each tab has a visible **split-rationale line** below the tab bar. Use whichever trigger actually fired:
525
+ - `"Split by UC count: 12 UCs > 8 threshold"` or
526
+ - `"Split by actor groups: [group A] / [group B] — disjoint responsibilities"`.
527
+ - Store all tabs in a `diagramLayouts` array (one entry per tab). Call `fixLayout(diagramLayouts[i])` for every tab on `DOMContentLoaded`. See §11 for the `showDiagram(index)` wiring.
528
+
529
+ ---
530
+
531
+ ## 8. Clickable Descriptions — Modal for UCs, Popups for Actors & Associations
532
+
533
+ v2 splits the click experience in two:
534
+
535
+ - **UCs → full-screen modal** showing the complete §4.4 documentation (id, name, primary actor, description, scenarios with flow-of-events, pre/post-conditions, user stories, optional wireframe). See §8a.
536
+ - **Actors and association lines → small floating popup** (single-line name + description), unchanged from v1.1.8. See §8b.
537
+
538
+ One entry point — `showDesc(id, type, event)` — routes to the right UI:
539
+
540
+ ```javascript
541
+ function showDesc(id, type, event) {
542
+ if (type === 'uc') { openUcModal(id); event.stopPropagation(); return; }
543
+ // Non-UC types use the small popup (§8b)
544
+ showPopup(id, type, event);
545
+ }
546
+ ```
547
+
548
+ All popup and modal functions (`showDesc`, `showPopup`, `hideDesc`, `openUcModal`,
549
+ `closeUcModal`, `pulseActor`, `wireframeSafe`) are provided by
550
+ `uc-diagram-common.js` — **do not inline them**.
551
+
552
+ ### 8b. Data objects for popups (inline in the HTML `<script>` block)
553
+
554
+ ```javascript
555
+ const actorDescriptions = {
556
+ 'customer': { name: 'לקוח', desc: 'לקוח הרשום במערכת. יוזם הזמנות ועוקב אחר סטטוס.' },
557
+ // ... one entry per actor
558
+ };
559
+ const assocDescriptions = {
560
+ // keyed by connection id; name = actor-name ↔ UC-name
561
+ 'a1': { name: 'לקוח ↔ פתיחת הזמנה', desc: 'הלקוח יוזם את פתיחת ההזמנה. תפקיד: Initiator.' },
562
+ // ... one entry per association line you want described
563
+ };
564
+ ```
565
+
566
+ ### 8a. useCaseDocs data object (inline in the HTML `<script>` block)
567
+
568
+ `openUcModal(id)` reads `useCaseDocs[id]` and renders a full-screen modal with
569
+ the complete UC spec. Populate one entry per UC:
570
+
571
+ ```javascript
572
+ const useCaseDocs = {
573
+ 'uc-place-order': {
574
+ id: 'uc-place-order',
575
+ name: 'פתיחת הזמנה',
576
+ primaryActor: 'customer',
577
+ secondaryActors: ['inventory'],
578
+ description: 'הלקוח פותח הזמנה חדשה ומציין פריטים.',
579
+ preconditions: ['הלקוח מחובר למערכת.'],
580
+ postconditions: ['ההזמנה נשמרה עם סטטוס "ממתינה".'],
581
+ scenarios: [
582
+ { id: 'sc-happy', name: 'מסלול מרכזי',
583
+ flow: [
584
+ { kind: 'actor', actor: 'customer', text: 'בוחר פריט מהקטלוג' },
585
+ { kind: 'system', text: 'מוסיף את הפריט לעגלה' },
586
+ { kind: 'include', uc: 'uc-authenticate', text: '' },
587
+ { kind: 'actor', actor: 'customer', text: 'מאשר את ההזמנה' }
588
+ ],
589
+ extensions: [
590
+ { at: 1, text: 'אם הפריט אזל במלאי — מציג הודעה וחוזר לשלב 1.' }
591
+ ]
592
+ }
593
+ ],
594
+ userStories: [
595
+ { role: 'לקוח', want: 'להזמין מוצר במהירות', so: 'שאקבל אותו תוך 48 שעות' }
596
+ ], // rendered as: "בתור לקוח, אני רוצה להזמין מוצר במהירות, כדי ששאקבל אותו תוך 48 שעות"
597
+ wireframe: '' // optional per §WF
598
+ }
599
+ // ... one entry per UC
600
+ };
601
+ ```
602
+
603
+ UC group: `<g id="uc-uc1" data-uc-id="uc1" onclick="showDesc('uc1','uc',event)" style="cursor:pointer">`
604
+ Actor group: `<g id="actor-student" data-actor-id="student" onclick="showDesc('student','actor',event)" style="cursor:pointer">`
605
+
606
+ ### Clickable association lines (required)
607
+
608
+ A 1.8px line is nearly impossible to click. Every associatable line gets **two** line elements: a visible thin line (the one `fixLayout` already updates via `data-conn-from` / `data-conn-to`) and a transparent thick hitbox on top.
609
+
610
+ ```html
611
+ <!-- Visible line — fixLayout writes x1/y1/x2/y2 here -->
612
+ <line data-conn-from="worker" data-conn-to="uc-receive"
613
+ x1="0" y1="0" x2="0" y2="0"
614
+ stroke="#334155" stroke-width="1.8"/>
615
+
616
+ <!-- Click hitbox — invisible, tracks the same endpoints, carries the id -->
617
+ <line class="conn-hitbox" data-conn-id="a1"
618
+ data-hitbox-from="worker" data-hitbox-to="uc-receive"
619
+ x1="0" y1="0" x2="0" y2="0"
620
+ stroke="transparent" stroke-width="14"
621
+ style="cursor:pointer"
622
+ onclick="showDesc('a1','assoc',event)"/>
623
+ ```
624
+
625
+ Inside `fixLayout` Step 8, copy the endpoints from each visible line to its matching hitbox so the clickable region tracks the visible line exactly:
626
+
627
+ ```javascript
628
+ // After writing x1/y1/x2/y2 on the visible line:
629
+ const hitbox = svg.querySelector(
630
+ `.conn-hitbox[data-hitbox-from="${fId}"][data-hitbox-to="${tId}"]`);
631
+ if (hitbox) {
632
+ hitbox.setAttribute('x1', x1); hitbox.setAttribute('y1', y1);
633
+ hitbox.setAttribute('x2', x2); hitbox.setAttribute('y2', y2);
634
+ }
635
+ ```
636
+
637
+ Draw order: visible line first, hitbox second, ellipses/actors last. The hitbox stays under the ellipses and actors but above the thin visible line so clicks on or near the line hit it.
638
+
639
+ Associations without a meaningful description (e.g., auto-generated from trivially obvious actor-UC pairs) may omit the hitbox and the `assocDescriptions` entry, but they still need to pass the §4.1 justification gate.
640
+
641
+ ### Timing labels on cron/time-actor associations (required)
642
+
643
+ `renderTimingLabels(layout)` is provided by `uc-diagram-common.js` — do not inline it.
644
+ It is called automatically at the end of every `fixLayout` invocation.
645
+
646
+ Every connection from a system-time actor MUST carry a `timing` field:
647
+
648
+ ```javascript
649
+ { from: 'cron', to: 'uc-daily-reconcile', type: 'association', timing: 'פעם ביום' }
650
+ ```
651
+
652
+ ---
653
+
654
+ ## §WF. Wireframe Authoring (Opt-In)
655
+
656
+ **Wireframes are generated only when the user explicitly requests them** (e.g., "add wireframes", "include UI mocks"). Do not generate wireframes unless asked — they significantly increase generation time.
657
+
658
+ When wireframes are requested, generate them only for UCs whose `primaryActor.actorType === 'human'`. Skip wireframes for:
659
+ - UCs whose primary actor is a system or cron (`actorType: 'system'`) — no human sees a screen.
660
+ - Pure include-target UCs (sub-behaviors with no actor association).
661
+
662
+ When generated, emit a wireframe as an inline HTML fragment and attach it to `useCaseDocs[ucId].wireframe`. The modal renderer (§8a) injects the fragment after passing a safety check (see `wireframeSafe()` in §8a).
663
+
664
+ Claude authors the wireframe using its own UI-generation judgment — there is no DSL. But the skill enforces a consistent look and safe sandbox via a scoped CSS theme and a set of forbidden patterns.
665
+
666
+ ### Root element
667
+
668
+ ```html
669
+ <div class="wf-screen">
670
+ <!-- optional header -->
671
+ <h3 class="wf-heading">כותרת המסך</h3>
672
+
673
+ <!-- form rows -->
674
+ <div class="wf-row">
675
+ <label class="wf-label">שם משתמש</label>
676
+ <input class="wf-input" disabled placeholder="" />
677
+ </div>
678
+ <div class="wf-row">
679
+ <label class="wf-label">סיסמה</label>
680
+ <input class="wf-input" disabled placeholder="" />
681
+ </div>
682
+
683
+ <!-- a table mock -->
684
+ <table class="wf-table">
685
+ <thead><tr><th>פריט</th><th>כמות</th><th>מחיר</th></tr></thead>
686
+ <tbody>
687
+ <tr><td>...</td><td>...</td><td>...</td></tr>
688
+ </tbody>
689
+ </table>
690
+
691
+ <!-- an image placeholder -->
692
+ <div class="wf-image-box">[לוגו / תמונה]</div>
693
+
694
+ <!-- buttons are inert visual placeholders only -->
695
+ <div class="wf-actions">
696
+ <button class="wf-button wf-primary">אישור</button>
697
+ <button class="wf-button">ביטול</button>
698
+ </div>
699
+
700
+ <!-- free-text hints / callouts -->
701
+ <p class="wf-hint">לחיצה על "אישור" שולחת את הטופס ועוברת למסך הסיכום.</p>
702
+ </div>
703
+ ```
704
+
705
+ ### Allowed CSS classes
706
+
707
+ Use only `wf-*` classes. The skill ships a scoped theme (§10) covering: `wf-screen`, `wf-heading`, `wf-row`, `wf-label`, `wf-input`, `wf-textarea`, `wf-select`, `wf-button`, `wf-primary`, `wf-actions`, `wf-table`, `wf-image-box`, `wf-hint`, `wf-section`, `wf-badge`.
708
+
709
+ ### Hard forbidden patterns
710
+
711
+ The `wireframeSafe()` regex rejects any of the following. A failing wireframe renders a placeholder and logs a warning — it is NEVER injected.
712
+
713
+ | Forbidden | Why |
714
+ |---|---|
715
+ | `<script>` | Can execute arbitrary JS in the host page. |
716
+ | `on*=` attributes (`onclick`, `onerror`, …) | Same. |
717
+ | `<iframe>` | Can load arbitrary content. |
718
+ | `src="http…"` / `href="http…"` | No external fetches — the HTML must remain self-contained. |
719
+ | `@import` | Same reason. |
720
+ | `<style>` block inside the wireframe | Styles come from the skill's `.wf-*` theme only, to keep wireframes visually consistent across UCs. |
721
+
722
+ ### Size and scope
723
+
724
+ - Keep each wireframe under ~200 lines of HTML. If the UC really needs more, split by scenario with `<h3 class="wf-heading">` dividers inside one `.wf-screen`.
725
+ - Wireframes are static visual mocks. Form fields use `disabled` so the cursor shows they are illustrative.
726
+ - Buttons are not wired to anything — `onclick` is forbidden. They communicate affordance, not behavior.
727
+ - **Visible text is Hebrew (CR #11).** Every heading, label, button, table header, and hint the user sees must be in Hebrew — the product is Hebrew. Class names (`wf-*`) and identifiers stay ASCII. RTL is inherited from `<body dir="rtl">`; do not override `direction`. The validator rejects a wireframe containing zero Hebrew characters (`wireframe-non-hebrew` error).
728
+
729
+ ### When NOT to include a wireframe
730
+
731
+ - Primary actor is a system / cron (`actorType: 'system'`) — e.g., a back-end reconciliation triggered by the clock.
732
+ - The UC is purely an `«include»` target (a sub-behavior, not a user-visible goal).
733
+
734
+ **"The UI is trivial" is not a valid reason to skip.** A single approval prompt still deserves a mock — it shows the reader what the button says, what data is visible on the screen, and what confirmation text appears. If you genuinely cannot author the wireframe because the spec is silent, record the gap in `diagramModel.openQuestions` AND still emit a placeholder wireframe with the questions visible inside it.
735
+
736
+ ---
737
+
738
+ ## 9. diagramModel (mandatory)
739
+
740
+ Define this variable in every generated HTML `<script>` block, following the `model-shape.md` spec for `kind: 'use-case'`. Every actor, use case, and relationship must be represented. The Visual Paradigm exporter walks this variable.
741
+
742
+ **v2 note:** each `kind: 'useCase'` element now carries the full documentation (`primaryActor`, `preconditions`, `postconditions`, `scenarios`, `userStories`, optional `wireframe`). `useCaseDocs` in §8a is the in-HTML runtime index keyed by UC id — it can be derived from `diagramModel.parts[*].elements.filter(e => e.kind === 'useCase')` during initialization, or emitted as a pre-built literal. Either way, each UC entry must satisfy the §4.4 gate.
743
+
744
+ ### Assumptions and open questions
745
+
746
+ Both `diagramModel` and `diagramLayout` carry two parallel string arrays, matching the BPMN skill's shape:
747
+
748
+ ```javascript
749
+ {
750
+ // ...
751
+ assumptions: [ 'המערכת מזהה כל עובד מחסן דרך מזהה משתמש יחיד.',
752
+ 'ייבוא נתונים מה-ERP של הספק מתבצע באמצעות API ולא קובץ.' ],
753
+ openQuestions: [ 'האם נדרש אישור נוסף על ידי מנהל לכל שינוי רמות מלאי?' ]
754
+ }
755
+ ```
756
+
757
+ Render them in a panel at the bottom of the page — see §11. Only render if at least one of the arrays is non-empty. Do NOT hide the panel; a reader reviewing the diagram needs to see which statements in the diagram rest on assumptions.
758
+
759
+ ---
760
+
761
+ ## 10. Styling
762
+
763
+ ```
764
+ Background: #f8fafc, dot-grid (24px, #cbd5e1, r=0.8)
765
+ System boundary: fill white, stroke #3b82f6 (2.5px), header fill #1e40af, rx=12
766
+ Ellipse: fill #d4e9f7, stroke #2980b9 (1.8px), solid
767
+ Ellipse text: #1a1a2e, 11px, weight 500, text-anchor middle
768
+ Actor stick figure: stroke #334155 (2px), human head fill #e0f0ff, system head fill #fef3c7
769
+ Actor label: #1e293b, 11px, weight 600, text-anchor middle
770
+
771
+ Actor SVG template — EXACT coordinates, do not deviate (fixLayout centers on the group origin):
772
+ <circle cx="0" cy="-60" r="14" fill="#e0f0ff" stroke="#334155" stroke-width="2"/>
773
+ <line x1="0" y1="-46" x2="0" y2="-10" stroke="#334155" stroke-width="2"/>
774
+ <line x1="-18" y1="-36" x2="18" y2="-36" stroke="#334155" stroke-width="2"/>
775
+ <line x1="0" y1="-10" x2="-14" y2="14" stroke="#334155" stroke-width="2"/>
776
+ <line x1="0" y1="-10" x2="14" y2="14" stroke="#334155" stroke-width="2"/>
777
+ <text x="0" y="30" text-anchor="middle" font-size="11" font-weight="600" fill="#1e293b"
778
+ font-family="'Noto Sans Hebrew',sans-serif">[Actor label]</text>
779
+ (For system actors: same but head fill #fef3c7, add «system» label at y="44" in IBM Plex Mono 9px #64748b)
780
+ Association line: #334155, 1.8px, no arrowhead
781
+ Include/extend: #7c3aed, 1.6px, dasharray 6 4, open arrowhead, label 9.5px
782
+ Generalization: #334155, 1.8px, hollow triangle (fill white)
783
+ System title: white on #1e40af, 14px bold
784
+ Assumptions panel: white bg, border #cbd5e1 (1px), rx=8, padding 16px, margin-top 20px
785
+ Assumptions h3: #1e40af, 13px, weight 600, margin-bottom 6px
786
+ Assumptions li: #334155, 12px, line-height 1.6, list-style disc inside
787
+ Fonts: 'Noto Sans Hebrew' + 'IBM Plex Mono' from Google Fonts
788
+ ```
789
+
790
+ All styles are in `https://themathbible.com/sad-mcp/uc-diagram-common.css` — **do not add a `<style>` block**. Do not inline any CSS.
791
+
792
+ Available wireframe classes (for §WF): `wf-screen`, `wf-heading`, `wf-row`, `wf-label`, `wf-input`, `wf-textarea`, `wf-select`, `wf-button`, `wf-primary`, `wf-actions`, `wf-table`, `wf-image-box`, `wf-hint`, `wf-section`, `wf-badge`.
793
+
794
+ ---
795
+
796
+ ## 11. HTML File Structure
797
+
798
+ ```html
799
+ <!DOCTYPE html>
800
+ <html lang="he" dir="rtl">
801
+ <head>
802
+ <meta charset="UTF-8">
803
+ <title>[System] — Use Case Diagram</title>
804
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Hebrew:wght@400;600&family=IBM+Plex+Mono:wght@400;700&display=swap" rel="stylesheet">
805
+ <link href="https://themathbible.com/sad-mcp/uc-diagram-common.css" rel="stylesheet">
806
+ <!-- REQUIRED: uc-diagram-common.css sets display:flex on .diagram-part, which overrides
807
+ the browser's [hidden]{display:none}. This one-liner restores correct tab switching. -->
808
+ <style>[hidden] { display: none !important; }</style>
809
+ </head>
810
+ <body>
811
+ <!-- Tab bar (only when split) -->
812
+ <div class="tab-bar">
813
+ <button class="tab active" onclick="showDiagram(0)">[Tab 1 label]</button>
814
+ <button class="tab" onclick="showDiagram(1)">[Tab 2 label]</button>
815
+ </div>
816
+
817
+ <!-- Split rationale (only when split) -->
818
+ <div class="split-rationale">Split by UC count: N UCs &gt; 8 threshold</div>
819
+
820
+ <!-- Fix Layout button -->
821
+ <div style="text-align:center;margin:8px 0;">
822
+ <button onclick="fixLayout(currentLayout())" class="fix-btn">Fix Layout</button>
823
+ </div>
824
+
825
+ <!-- One container per tab; inactive tabs are hidden. -->
826
+ <div class="diagram-part" id="diagram-0">
827
+ <svg id="diagram-svg-0" viewBox="0 0 1100 900" xmlns="http://www.w3.org/2000/svg">
828
+ <!-- dot grid, boundary, association lines, include/extend lines,
829
+ generalization lines, UC ellipse groups, actor groups -->
830
+ </svg>
831
+ </div>
832
+ <div class="diagram-part" id="diagram-1" hidden>
833
+ <svg id="diagram-svg-1" viewBox="0 0 1100 900" xmlns="http://www.w3.org/2000/svg">...</svg>
834
+ </div>
835
+
836
+ <!-- Assumptions / open-questions panel -->
837
+ <div id="assumptions-panel" class="assumptions-panel" hidden>
838
+ <div class="assumptions-col" id="assumptions-col">
839
+ <h3>הנחות</h3><ul id="assumptions-list"></ul>
840
+ </div>
841
+ <div class="assumptions-col" id="open-questions-col">
842
+ <h3>שאלות פתוחות</h3><ul id="open-questions-list"></ul>
843
+ </div>
844
+ </div>
845
+
846
+ <!-- User stories summary — populated automatically by renderUserStoriesSummary()
847
+ in common.js, which deduplicates across all UCs. Do not populate inline. -->
848
+ <div id="user-stories-panel" class="user-stories-panel" hidden>
849
+ <h3>סיפורי משתמש</h3>
850
+ <ul id="user-stories-list"></ul>
851
+ </div>
852
+
853
+ <!-- UC modal (body is populated by openUcModal in common.js; starts empty) -->
854
+ <div id="uc-modal" class="uc-modal" hidden role="dialog" aria-modal="true">
855
+ <div class="uc-modal-backdrop" onclick="closeUcModal()"></div>
856
+ <div class="uc-modal-body"></div>
857
+ </div>
858
+
859
+ <!-- VP export button -->
860
+ <button class="vp-export-btn" onclick="exportXMI()">Download .xmi (Visual Paradigm)</button>
861
+
862
+ <!-- DIAGRAM DATA ONLY — all functions are in uc-diagram-common.js.
863
+ Use var (NOT const/let) so these become window properties accessible from the external script.
864
+ Do NOT add activeTab, currentLayout, showDiagram, exportXMI, xmlEscape, downloadFile,
865
+ or any DOMContentLoaded handler here — they all live in uc-diagram-common.js.
866
+ Adding let/const variables with the same names as any variable in the external script
867
+ causes a SyntaxError that silently kills the entire external script. -->
868
+ <script>
869
+ var diagramLayouts = [ /* one layout object per tab, per §6 */ ];
870
+ var actorDescriptions = { /* §8b — one entry per actor */ };
871
+ var assocDescriptions = { /* §8b — one entry per association line */ };
872
+ var useCaseDocs = { /* §8a — full per-UC documentation */ };
873
+ var diagramModel = { /* model-shape.md, kind: 'use-case' */ };
874
+ </script>
875
+ <script src="https://themathbible.com/sad-mcp/uc-diagram-common.js"></script>
876
+ </body>
877
+ </html>
878
+ ```
879
+
880
+ **Single-tab diagrams still define the full script.** Always emit all five `var` data globals; the tab bar HTML and second `.diagram-part` are the only things conditional on splitting. `showDiagram`, `currentLayout`, `activeTab`, `fixLayout`, `exportXMI` and all other functions live exclusively in `uc-diagram-common.js` — never re-declare them inline.
881
+
882
+ ---
883
+
884
+ ## 12. Output
885
+
886
+ **Mandatory validator step — call before writing the HTML.**
887
+
888
+ After assembling `diagramModel` and before generating any HTML, serialize it to JSON and call the MCP tool `uml_usecase_validate_model`:
889
+
890
+ ```
891
+ uml_usecase_validate_model(model_json = JSON.stringify(diagramModel))
892
+ ```
893
+
894
+ The validator runs locally (no API cost) and catches what the §4 and CR gates cannot enforce from prose:
895
+ - Include target with an actor association (CR #4) → ERROR
896
+ - Single-incoming «include» (CR #4) → ERROR
897
+ - Send-only UC names like `Print Shipping Label` / `שליחת X` (CR #10) → ERROR
898
+ - Physical-verb UC names (`Perform X`, `ביצוע X`) not wrapped with a system verb (CR #8) → WARNING
899
+ - Primary actor missing, unknown, or not connected as initiator (§4.1/§4.4) → ERROR
900
+ - Flow step references to unknown actor/UC ids, or `«include»` steps without a matching `include` relationship → ERROR
901
+ - Extensions referencing invalid step numbers → ERROR
902
+ - Narrative text missing Hebrew characters (description, preconditions, postconditions, flow/extension text, user stories, assumptions, openQuestions) → ERROR
903
+ - Unsafe wireframe HTML (script / on*= / iframe / external URL / @import / inline style) → ERROR
904
+ - Warnings: fewer than 2 user stories on a UC, no preconditions/postconditions, overall >2 include+extend relationships, wireframe present on a UC when none were requested
905
+
906
+ If the response is `✗ Model invalid`, fix every listed error and call the validator again. **Do not write the HTML file until the validator returns `✓ Model valid`.**
907
+
908
+ After the validator passes, save the HTML as `[system_name]_use_case_diagram.html`.
909
+ Briefly report: actor count, use case count, include/extend count, whether split, and any warnings the validator surfaced.
910
+
911
+ ---
912
+
913
+ ## 13. Pre-Delivery Checklist
914
+
915
+ - [ ] `uml_usecase_validate_model` was called and returned `✓ Model valid` on the final model (§12). Warnings may remain but every ERROR is fixed.
916
+ - [ ] All narrative UC documentation (description, preconditions, postconditions, flow-step text, extension text, user stories, assumptions, open questions) is in Hebrew (CR #11). Identifier strings remain ASCII.
917
+ - [ ] User stories cover every distinct stakeholder goal behind the UC — not a single token story. If the UC serves multiple roles or scenarios, each gets its own story.
918
+ - [ ] Every UC has a filled §4.4 documentation block AND a matching `useCaseDocs[id]` entry with `primaryActor`, ≥1 scenario, ≥1 flow step, `preconditions`, `postconditions`, and `userStories`.
919
+ - [ ] Every `primaryActor` id exists in `actors[]` AND has an association with `role: 'initiator'` on that UC (aligning §4.1 with §4.4).
920
+ - [ ] Every flow step of `kind: 'actor'` references a real actor id; every `kind: 'include'` references a real UC id; every `extension.at` references a valid step number in its scenario's flow.
921
+ - [ ] Clicking a UC on the diagram opens the full-screen modal (§8a); Esc / backdrop click closes it. Step actor/UC references are clickable. Actor popup + association popup (§8b) still work for non-UC clicks.
922
+ - [ ] Wireframes generated only if the user explicitly requested them (§WF). If generated, only for human-initiated UCs; system/cron and include-targets omitted.
923
+ - [ ] Any `wireframe` HTML passes `wireframeSafe()` (no `<script>`, no `on*=`, no `<iframe>`, no external URLs, no `@import`, no inline `<style>`); all classes are `wf-*` only; visible text is in Hebrew.
924
+ - [ ] Every actor-UC association has a written §4.1 justification block (role, initiates? one-line "why"); every UC has ≥1 association with `initiates = yes`.
925
+ - [ ] Every association line carries a transparent click-hitbox (stroke-width ≥12, `data-conn-id`) wired to `showDesc(id,'assoc',event)`; `assocDescriptions` entry exists for each id.
926
+ - [ ] `assumptions` and `openQuestions` arrays are present in `diagramModel`; the HTML renders the bottom panel whenever either is non-empty.
927
+ - [ ] `#user-stories-panel` / `#user-stories-list` elements are present in the HTML; `renderUserStoriesSummary()` (called automatically) will populate them with a deduplicated list of all user stories across all UCs. Do not manually populate this list.
928
+ - [ ] Every «include» and «extend» has a written §4 justification block; any that cannot be filled in has been deleted.
929
+ - [ ] No «include» with exactly one incoming arrow — every include target has ≥2 bases AND zero actor associations.
930
+ - [ ] No generic "ניהול הגדרות מערכת" or combined-entity UCs; each entity has its own UC.
931
+ - [ ] Every UC has ≥1 association line to an actor (no orphaned ellipses).
932
+ - [ ] All ellipses have solid borders (zero dashed ellipses).
933
+ - [ ] SVG draw order: boundary → lines → ellipses → actors.
934
+ - [ ] Ellipse endpoints computed via boundary intersection (not drawn to center).
935
+ - [ ] `fixLayout` runs on `DOMContentLoaded` and on the Fix Layout button; uses named constants (HEADER_H, UC_PAD, UC_GAP, ACTOR_PAD), not magic numbers.
936
+ - [ ] UC and actor description popups wired to every `data-uc-id` / `data-actor-id` element.
937
+ - [ ] System actors drawn as stick figures with `«system»` label (not rectangles).
938
+ - [ ] Every UC label describes an action WITH the information system (data entry, query, state change) — no bare physical verbs (`הכנת X`, `ספירת X`, `מסירת X`, `תשלום במזומן`). Physical verbs are wrapped with a system verb (`רישום`, `הזנה`, `עדכון`, `סימון`, etc.).
939
+ - [ ] Every system-time / cron actor association has a `timing` field (and the rendered diagram shows a visible label at the line midpoint) — no unlabeled cron connections.
940
+ - [ ] No "send-only" UCs (no bare `שליחת X` / `הודעה ל-X`); message-sends are folded into the UC that decided to send.
941
+ - [ ] `showDiagram(index)` is defined in every generated file, even single-tab diagrams (so tab switching is wired if splitting is ever added).
942
+ - [ ] `fixLayout` uses adaptive column X (widens boundary if needed so left/center/right ellipses never overlap horizontally) AND expands viewBox width so right-side actors are not clipped.
943
+ - [ ] If split: each tab has a split-rationale line citing the actual trigger that fired (count > 8, or 2+ disjoint actor groups); `showDiagram` and per-tab `fixLayout` wired per §11.
944
+ - [ ] `diagramModel` defined per `model-shape.md`; VP export button present and wired to `exportXMI()`.