sad-mcp 1.1.7 → 2.0.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
|
@@ -134,12 +134,63 @@ function xmiBody(elements, rels) {
|
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
### Use-case xmiBody
|
|
137
|
+
|
|
138
|
+
v2 emits the rich per-UC documentation (preconditions, postconditions, flow of events) as nested UML elements so Visual Paradigm's Specification pane populates on import.
|
|
139
|
+
|
|
137
140
|
```javascript
|
|
138
141
|
function xmiBody(elements, rels) {
|
|
139
142
|
const lines = [];
|
|
143
|
+
|
|
144
|
+
// Helper: serialize scenarios to a numbered plain-text body for OpaqueBehavior.
|
|
145
|
+
function flowText(uc) {
|
|
146
|
+
const actorName = id => (elements.find(e => e.kind === 'actor' && e.id === id) || {}).name || id;
|
|
147
|
+
const ucName = id => (elements.find(e => e.kind === 'useCase' && e.id === id) || {}).name || id;
|
|
148
|
+
const lines = [];
|
|
149
|
+
(uc.scenarios || []).forEach(sc => {
|
|
150
|
+
lines.push((sc.name || sc.id) + ':');
|
|
151
|
+
(sc.flow || []).forEach((step, i) => {
|
|
152
|
+
const n = i + 1;
|
|
153
|
+
if (step.kind === 'actor') lines.push(` ${n}. ${actorName(step.actor)}: ${step.text}`);
|
|
154
|
+
else if (step.kind === 'system') lines.push(` ${n}. System: ${step.text}`);
|
|
155
|
+
else if (step.kind === 'include') lines.push(` ${n}. «include» ${ucName(step.uc)}${step.text ? ' — ' + step.text : ''}`);
|
|
156
|
+
else if (step.kind === 'extension') lines.push(` ${n}a. ${step.text}`);
|
|
157
|
+
(sc.extensions || []).filter(e => e.at === n).forEach((e, j) => {
|
|
158
|
+
const letter = String.fromCharCode(97 + j);
|
|
159
|
+
lines.push(` ${n}${letter}. ${e.text}`);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return lines.join('\n');
|
|
164
|
+
}
|
|
165
|
+
|
|
140
166
|
for (const el of elements) {
|
|
141
|
-
if (el.kind === 'useCase')
|
|
142
|
-
|
|
167
|
+
if (el.kind === 'useCase') {
|
|
168
|
+
const hasDocs = (el.preconditions && el.preconditions.length)
|
|
169
|
+
|| (el.postconditions && el.postconditions.length)
|
|
170
|
+
|| (el.scenarios && el.scenarios.length);
|
|
171
|
+
if (!hasDocs) {
|
|
172
|
+
lines.push(` <packagedElement xmi:type="uml:UseCase" xmi:id="${el.id}" name="${xmlEscape(el.name)}"/>`);
|
|
173
|
+
} else {
|
|
174
|
+
lines.push(` <packagedElement xmi:type="uml:UseCase" xmi:id="${el.id}" name="${xmlEscape(el.name)}">`);
|
|
175
|
+
(el.preconditions || []).forEach((p, i) => {
|
|
176
|
+
lines.push(` <precondition xmi:type="uml:Constraint" xmi:id="${el.id}-pre-${i+1}" name="pre-${i+1}">`);
|
|
177
|
+
lines.push(` <specification xmi:type="uml:OpaqueExpression" xmi:id="${el.id}-pre-${i+1}-s"><body>${xmlEscape(p)}</body></specification>`);
|
|
178
|
+
lines.push(` </precondition>`);
|
|
179
|
+
});
|
|
180
|
+
(el.postconditions || []).forEach((p, i) => {
|
|
181
|
+
lines.push(` <postcondition xmi:type="uml:Constraint" xmi:id="${el.id}-post-${i+1}" name="post-${i+1}">`);
|
|
182
|
+
lines.push(` <specification xmi:type="uml:OpaqueExpression" xmi:id="${el.id}-post-${i+1}-s"><body>${xmlEscape(p)}</body></specification>`);
|
|
183
|
+
lines.push(` </postcondition>`);
|
|
184
|
+
});
|
|
185
|
+
if (el.scenarios && el.scenarios.length) {
|
|
186
|
+
const body = flowText(el);
|
|
187
|
+
lines.push(` <ownedBehavior xmi:type="uml:OpaqueBehavior" xmi:id="${el.id}-flow" name="Flow of Events">`);
|
|
188
|
+
lines.push(` <body>${xmlEscape(body)}</body>`);
|
|
189
|
+
lines.push(` </ownedBehavior>`);
|
|
190
|
+
}
|
|
191
|
+
lines.push(` </packagedElement>`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
143
194
|
if (el.kind === 'actor')
|
|
144
195
|
lines.push(` <packagedElement xmi:type="uml:Actor" xmi:id="${el.id}" name="${xmlEscape(el.name)}"/>`);
|
|
145
196
|
}
|
|
@@ -63,6 +63,8 @@ const diagramModel = {
|
|
|
63
63
|
|
|
64
64
|
### Use-case diagram elements
|
|
65
65
|
|
|
66
|
+
**v2 schema** — every UC carries full Visual Paradigm-style documentation (primary actor, scenarios with flow-of-events, pre/post-conditions, user stories, optional wireframe). The doc fields populate the per-UC modal and the XMI export. Required: `primaryActor`, `scenarios` (≥1 with ≥1 flow step), `preconditions`, `postconditions`, `userStories`. Optional: `secondaryActors`, `wireframe`.
|
|
67
|
+
|
|
66
68
|
```javascript
|
|
67
69
|
// Element subtypes for kind: 'use-case'
|
|
68
70
|
{ kind: 'actor', id, name,
|
|
@@ -70,13 +72,43 @@ const diagramModel = {
|
|
|
70
72
|
side: 'left' | 'right',
|
|
71
73
|
x, y }
|
|
72
74
|
|
|
73
|
-
{ kind: 'useCase', id, name,
|
|
74
|
-
|
|
75
|
+
{ kind: 'useCase', id, name,
|
|
76
|
+
description: '', // short summary
|
|
77
|
+
|
|
78
|
+
// v2 documentation fields:
|
|
79
|
+
primaryActor: 'actorId', // exactly one, must exist in elements
|
|
80
|
+
secondaryActors: [], // optional actor ids
|
|
81
|
+
preconditions: [ 'string' ],
|
|
82
|
+
postconditions: [ 'string' ],
|
|
83
|
+
|
|
84
|
+
scenarios: [ // ≥1 scenario required
|
|
85
|
+
{ id, name,
|
|
86
|
+
flow: [ // ≥1 step required
|
|
87
|
+
{ kind: 'actor', actor: 'actorId', text: '' },
|
|
88
|
+
{ kind: 'system', text: '' },
|
|
89
|
+
{ kind: 'include', uc: 'ucId', text: '' },
|
|
90
|
+
{ kind: 'extension', at: 2, text: '' } // at = step number extended
|
|
91
|
+
],
|
|
92
|
+
extensions: [ // optional, alternative to inline extension steps
|
|
93
|
+
{ at: 2, text: '' }
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
userStories: [
|
|
99
|
+
{ role: '', want: '', so: '' }
|
|
100
|
+
],
|
|
101
|
+
|
|
102
|
+
wireframe: '', // optional inline HTML fragment, see SKILL.md §WF
|
|
103
|
+
|
|
104
|
+
x, y, rx, ry } // rx/ry = ellipse radii (diagram layout)
|
|
75
105
|
|
|
76
106
|
{ kind: 'system', id, name, x, y, w, h } // system boundary rectangle
|
|
77
107
|
|
|
78
108
|
// Relationship subtypes
|
|
79
|
-
{ kind: 'actorAssoc', id, actorId, ucId
|
|
109
|
+
{ kind: 'actorAssoc', id, actorId, ucId,
|
|
110
|
+
role: 'initiator' | 'participant' | 'recipient' | 'supplier',
|
|
111
|
+
description: '' } // click-popup text
|
|
80
112
|
{ kind: 'include', id, from, to } // from = including UC, to = included
|
|
81
113
|
{ kind: 'extend', id, from, to, extensionPoint: '' }
|
|
82
114
|
{ kind: 'generalization',id, from, to } // actors or UCs; from = child, to = parent
|
|
@@ -206,6 +206,35 @@ Reason: Critical for consistency in distributed/international teams
|
|
|
206
206
|
|
|
207
207
|
---
|
|
208
208
|
|
|
209
|
+
## 11a. UC Documentation (Visual Paradigm convention)
|
|
210
|
+
|
|
211
|
+
Beyond the diagram shapes, each use case carries a **written specification** — this is what Visual Paradigm stores in the UC's "Specification" pane and what students include in exam answers. The skill's §4.4 block captures the same structure:
|
|
212
|
+
|
|
213
|
+
| Field | VP name | Convention |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| Primary actor | Primary actor | The actor who **initiates** the UC (single). Secondary actors listed separately. |
|
|
216
|
+
| Description | Description | One-sentence summary of the goal. |
|
|
217
|
+
| Preconditions | Preconditions | Bulleted list — must hold BEFORE the UC starts. |
|
|
218
|
+
| Postconditions | Postconditions | Bulleted list — must hold AFTER the UC completes successfully. |
|
|
219
|
+
| Flow of events | Flow of Events / Basic Flow | Numbered steps. Each step names who does what. Actor references and include/extend cross-references are explicit, like pseudo-code. |
|
|
220
|
+
| Extensions | Extensions / Alternative Flows | Sub-steps labelled `Na`, `Nb`, … under the step they branch from. |
|
|
221
|
+
| User stories | (project-level, not per-UC in VP) | Added by the skill so students can trace each UC back to its requirement. |
|
|
222
|
+
|
|
223
|
+
**Flow-of-events step style** — taught by the professor (Lecture 4, slides 210-215, coffee shop example):
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
1. Customer selects a product from the catalog.
|
|
227
|
+
2. System adds the product to the cart.
|
|
228
|
+
3. «include» Authenticate Customer.
|
|
229
|
+
4. Customer confirms the order.
|
|
230
|
+
4a. If payment method is rejected — notify customer and end in "Not Completed" status.
|
|
231
|
+
5. System saves the order and returns an order ID.
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The in-HTML modal (§8a of the SKILL) renders this style verbatim.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
209
238
|
## 11. SUMMARY
|
|
210
239
|
|
|
211
240
|
Professor teaches UML use case notation through:
|
|
@@ -87,11 +87,29 @@ The shared files prepended to this prompt define the layout recipes, model shape
|
|
|
87
87
|
|
|
88
88
|
---
|
|
89
89
|
|
|
90
|
-
## 4.
|
|
90
|
+
## 4. Relationship Gates — Mandatory Justifications
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
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).
|
|
93
93
|
|
|
94
|
-
###
|
|
94
|
+
### 4.1 Association justification (actor ↔ UC)
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
Association: [actor id] ↔ [UC id]
|
|
98
|
+
Actor role: initiator / participant / recipient / supplier
|
|
99
|
+
Does this actor TRIGGER / INITIATE the UC? yes / no
|
|
100
|
+
One-line justification: "The actor ___ in order to ___"
|
|
101
|
+
Description shown on click (Hebrew): ___________________
|
|
102
|
+
|
|
103
|
+
→ If the actor has no meaningful interaction with the UC → DELETE the association.
|
|
104
|
+
→ Every UC MUST have ≥1 association where "initiates = yes". A UC with no initiator is orphaned — fix it or delete the UC.
|
|
105
|
+
→ 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).
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
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`.
|
|
109
|
+
|
|
110
|
+
### 4.2 «include» justification
|
|
111
|
+
|
|
112
|
+
**Default to zero.** Treat every «include» as guilty until proven innocent.
|
|
95
113
|
|
|
96
114
|
```
|
|
97
115
|
«include» candidate: __________________________________
|
|
@@ -109,7 +127,7 @@ Semantics: «include» means the target runs **every single time** the base runs
|
|
|
109
127
|
|
|
110
128
|
Arrow: dashed, open arrowhead, `Base UC → Included UC`.
|
|
111
129
|
|
|
112
|
-
### «extend» justification
|
|
130
|
+
### 4.3 «extend» justification
|
|
113
131
|
|
|
114
132
|
```
|
|
115
133
|
«extend» candidate: __________________________________
|
|
@@ -125,6 +143,49 @@ Arrow: dashed, open arrowhead, `Base UC → Included UC`.
|
|
|
125
143
|
|
|
126
144
|
Arrow: dashed, open arrowhead, `Extension UC → Base UC`. Use at most once or twice per diagram.
|
|
127
145
|
|
|
146
|
+
### 4.4 UC documentation block (mandatory)
|
|
147
|
+
|
|
148
|
+
**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.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
UC documentation: [uc-id]
|
|
152
|
+
name: _______
|
|
153
|
+
primaryActor: _______ (must be an actor connected to this UC with role: 'initiator' per §4.1)
|
|
154
|
+
secondaryActors: [...] (actors with role: 'participant' / 'recipient' / 'supplier')
|
|
155
|
+
description: one-sentence summary of what the UC does
|
|
156
|
+
|
|
157
|
+
preconditions:
|
|
158
|
+
- ___________________
|
|
159
|
+
- ___________________
|
|
160
|
+
postconditions:
|
|
161
|
+
- ___________________
|
|
162
|
+
|
|
163
|
+
scenarios:
|
|
164
|
+
- id: sc-happy, name: "מסלול מרכזי (Happy Path)"
|
|
165
|
+
flow:
|
|
166
|
+
1. [actor:primaryActor] ___________________
|
|
167
|
+
2. [system] ___________________
|
|
168
|
+
3. [include:uc-xxx] ___________________ (only if §4.2 gate passed)
|
|
169
|
+
4. [actor:secondary] ___________________
|
|
170
|
+
extensions:
|
|
171
|
+
2a. if ___________ then ___________
|
|
172
|
+
4a. if ___________ then ___________
|
|
173
|
+
- id: sc-alt-1, name: "מסלול חלופי: ___" (optional additional scenarios)
|
|
174
|
+
|
|
175
|
+
userStories:
|
|
176
|
+
- As a [role], I want [want], so that [benefit].
|
|
177
|
+
|
|
178
|
+
wireframe?: (optional — only where the UC benefits from a UI mock; see §WF)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Gates enforced before publishing:**
|
|
182
|
+
- Exactly one `primaryActor`; the actor exists in `actors[]` AND an association exists with `role: 'initiator'` on this UC (§4.1).
|
|
183
|
+
- At least one scenario with at least one flow step.
|
|
184
|
+
- Every `[actor:id]` in a flow step references a real actor id.
|
|
185
|
+
- Every `[include:id]` in a flow step references a real UC id AND corresponds to an `«include»` relationship that passed §4.2.
|
|
186
|
+
- Every `Na` extension references a valid step number `N` in the same scenario's flow.
|
|
187
|
+
- At least one user story per UC.
|
|
188
|
+
|
|
128
189
|
### Quick disqualifiers
|
|
129
190
|
|
|
130
191
|
- Exactly one incoming «include» arrow → fold into the parent.
|
|
@@ -414,23 +475,41 @@ If neither trigger fires, produce a single tab. Do not split on gut feeling.
|
|
|
414
475
|
|
|
415
476
|
---
|
|
416
477
|
|
|
417
|
-
## 8. Clickable Descriptions for UCs
|
|
478
|
+
## 8. Clickable Descriptions — Modal for UCs, Popups for Actors & Associations
|
|
418
479
|
|
|
419
|
-
|
|
480
|
+
v2 splits the click experience in two:
|
|
481
|
+
|
|
482
|
+
- **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.
|
|
483
|
+
- **Actors and association lines → small floating popup** (single-line name + description), unchanged from v1.1.8. See §8b.
|
|
484
|
+
|
|
485
|
+
One entry point — `showDesc(id, type, event)` — routes to the right UI:
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
function showDesc(id, type, event) {
|
|
489
|
+
if (type === 'uc') { openUcModal(id); event.stopPropagation(); return; }
|
|
490
|
+
// Non-UC types use the small popup (§8b)
|
|
491
|
+
showPopup(id, type, event);
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 8b. Small popup for actors and associations
|
|
420
496
|
|
|
421
497
|
```javascript
|
|
422
|
-
const useCaseDescriptions = {
|
|
423
|
-
'uc1': { name: 'פתיחת הזמנה', desc: 'המשתמש פותח הזמנה חדשה ומציין פרטי המוצרים הנדרשים.' },
|
|
424
|
-
// ... one entry per UC
|
|
425
|
-
};
|
|
426
498
|
const actorDescriptions = {
|
|
427
499
|
'customer': { name: 'לקוח', desc: 'לקוח הרשום במערכת. יוזם הזמנות ועוקב אחר סטטוס.' },
|
|
428
500
|
// ... one entry per actor
|
|
429
501
|
};
|
|
502
|
+
const assocDescriptions = {
|
|
503
|
+
// keyed by connection id; name = actor-name ↔ UC-name (auto-composed is fine)
|
|
504
|
+
'a1': { name: 'לקוח ↔ פתיחת הזמנה', desc: 'הלקוח יוזם את פתיחת ההזמנה. תפקיד: Initiator.' },
|
|
505
|
+
// ... one entry per association line you want described
|
|
506
|
+
};
|
|
430
507
|
|
|
431
|
-
function
|
|
508
|
+
function showPopup(id, type, event) {
|
|
432
509
|
hideDesc();
|
|
433
|
-
const data = type === '
|
|
510
|
+
const data = type === 'actor' ? actorDescriptions[id]
|
|
511
|
+
: type === 'assoc' ? assocDescriptions[id]
|
|
512
|
+
: null;
|
|
434
513
|
if (!data) return;
|
|
435
514
|
const panel = document.createElement('div');
|
|
436
515
|
panel.id = 'desc-panel';
|
|
@@ -454,13 +533,210 @@ function showDesc(id, type, event) {
|
|
|
454
533
|
}
|
|
455
534
|
function hideDesc() { document.getElementById('desc-panel')?.remove(); }
|
|
456
535
|
document.addEventListener('click', e => {
|
|
457
|
-
if (!e.target.closest('[data-uc-id]') &&
|
|
536
|
+
if (!e.target.closest('[data-uc-id]') &&
|
|
537
|
+
!e.target.closest('[data-actor-id]') &&
|
|
538
|
+
!e.target.closest('[data-conn-id]')) hideDesc();
|
|
458
539
|
});
|
|
459
540
|
```
|
|
460
541
|
|
|
542
|
+
### 8a. Full-screen modal for UCs
|
|
543
|
+
|
|
544
|
+
Every UC ellipse's click routes to `openUcModal(id)`, which reads `useCaseDocs[id]` (populated from the §4.4 documentation blocks) and renders a full-screen modal with the complete UC spec. The modal has sections for: header (id + name + primary actor link), description, scenarios (numbered flow with typed steps + extensions), a row with preconditions / postconditions / user stories, and an optional wireframe.
|
|
545
|
+
|
|
546
|
+
```javascript
|
|
547
|
+
const useCaseDocs = {
|
|
548
|
+
'uc-place-order': {
|
|
549
|
+
id: 'uc-place-order',
|
|
550
|
+
name: 'פתיחת הזמנה',
|
|
551
|
+
primaryActor: 'customer',
|
|
552
|
+
secondaryActors: ['inventory'],
|
|
553
|
+
description: 'הלקוח פותח הזמנה חדשה ומציין פריטים.',
|
|
554
|
+
preconditions: ['הלקוח מחובר למערכת.'],
|
|
555
|
+
postconditions: ['ההזמנה נשמרה עם סטטוס "ממתינה".'],
|
|
556
|
+
scenarios: [
|
|
557
|
+
{ id: 'sc-happy', name: 'מסלול מרכזי',
|
|
558
|
+
flow: [
|
|
559
|
+
{ kind: 'actor', actor: 'customer', text: 'בוחר פריט מהקטלוג' },
|
|
560
|
+
{ kind: 'system', text: 'מוסיף את הפריט לעגלה' },
|
|
561
|
+
{ kind: 'include', uc: 'uc-authenticate', text: '' },
|
|
562
|
+
{ kind: 'actor', actor: 'customer', text: 'מאשר את ההזמנה' }
|
|
563
|
+
],
|
|
564
|
+
extensions: [
|
|
565
|
+
{ at: 1, text: 'אם הפריט אזל במלאי — מציג הודעה וחוזר לשלב 1.' }
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
],
|
|
569
|
+
userStories: [
|
|
570
|
+
{ role: 'לקוח', want: 'להזמין מוצר במהירות', so: 'שאקבל אותו תוך 48 שעות.' }
|
|
571
|
+
],
|
|
572
|
+
wireframe: '' // optional per §WF
|
|
573
|
+
}
|
|
574
|
+
// ... one entry per UC
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
function openUcModal(id) {
|
|
578
|
+
const doc = useCaseDocs[id];
|
|
579
|
+
if (!doc) return;
|
|
580
|
+
const actorName = aid => (actorDescriptions[aid]?.name) || aid;
|
|
581
|
+
const ucName = uid => (useCaseDocs[uid]?.name) || uid;
|
|
582
|
+
const modal = document.getElementById('uc-modal');
|
|
583
|
+
modal.querySelector('.uc-id').textContent = doc.id;
|
|
584
|
+
modal.querySelector('.uc-name').textContent = doc.name;
|
|
585
|
+
const paLink = modal.querySelector('.uc-primary-link');
|
|
586
|
+
paLink.textContent = actorName(doc.primaryActor);
|
|
587
|
+
paLink.dataset.actorId = doc.primaryActor;
|
|
588
|
+
paLink.onclick = () => { closeUcModal(); pulseActor(doc.primaryActor); };
|
|
589
|
+
|
|
590
|
+
modal.querySelector('.uc-description').textContent = doc.description || '';
|
|
591
|
+
|
|
592
|
+
// Scenarios
|
|
593
|
+
const sc = modal.querySelector('.uc-scenarios');
|
|
594
|
+
sc.innerHTML = '';
|
|
595
|
+
(doc.scenarios || []).forEach(sce => {
|
|
596
|
+
const h = document.createElement('h3'); h.textContent = sce.name || sce.id; sc.appendChild(h);
|
|
597
|
+
const ol = document.createElement('ol');
|
|
598
|
+
(sce.flow || []).forEach((step, i) => {
|
|
599
|
+
const li = document.createElement('li');
|
|
600
|
+
if (step.kind === 'actor') {
|
|
601
|
+
const strong = document.createElement('strong');
|
|
602
|
+
strong.className = 'step-actor';
|
|
603
|
+
strong.dataset.actorId = step.actor;
|
|
604
|
+
strong.textContent = actorName(step.actor);
|
|
605
|
+
strong.onclick = () => { closeUcModal(); pulseActor(step.actor); };
|
|
606
|
+
li.appendChild(strong);
|
|
607
|
+
li.appendChild(document.createTextNode(': ' + (step.text || '')));
|
|
608
|
+
} else if (step.kind === 'system') {
|
|
609
|
+
const em = document.createElement('em');
|
|
610
|
+
em.className = 'step-system'; em.textContent = 'המערכת';
|
|
611
|
+
li.appendChild(em);
|
|
612
|
+
li.appendChild(document.createTextNode(': ' + (step.text || '')));
|
|
613
|
+
} else if (step.kind === 'include') {
|
|
614
|
+
const span = document.createElement('span');
|
|
615
|
+
span.className = 'step-include'; span.textContent = '«include» ';
|
|
616
|
+
const a = document.createElement('a');
|
|
617
|
+
a.className = 'step-uc-ref'; a.textContent = ucName(step.uc);
|
|
618
|
+
a.onclick = () => openUcModal(step.uc);
|
|
619
|
+
li.appendChild(span); li.appendChild(a);
|
|
620
|
+
if (step.text) li.appendChild(document.createTextNode(' — ' + step.text));
|
|
621
|
+
} else if (step.kind === 'extension') {
|
|
622
|
+
li.className = 'ext-inline';
|
|
623
|
+
li.textContent = (step.at ? step.at + 'a. ' : '') + (step.text || '');
|
|
624
|
+
}
|
|
625
|
+
ol.appendChild(li);
|
|
626
|
+
// Inline extensions defined in sce.extensions anchored to this step number:
|
|
627
|
+
(sce.extensions || []).filter(e => e.at === i + 1).forEach((ext, j) => {
|
|
628
|
+
const sub = document.createElement('li');
|
|
629
|
+
sub.className = 'ext-sub';
|
|
630
|
+
const letter = String.fromCharCode(97 + j); // a, b, c, ...
|
|
631
|
+
sub.textContent = (i + 1) + letter + '. ' + ext.text;
|
|
632
|
+
ol.appendChild(sub);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
sc.appendChild(ol);
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
const fillList = (selector, items) => {
|
|
639
|
+
const el = modal.querySelector(selector);
|
|
640
|
+
el.innerHTML = '';
|
|
641
|
+
(items || []).forEach(s => { const li = document.createElement('li'); li.textContent = s; el.appendChild(li); });
|
|
642
|
+
};
|
|
643
|
+
fillList('.uc-pre ul', doc.preconditions);
|
|
644
|
+
fillList('.uc-post ul', doc.postconditions);
|
|
645
|
+
const usList = modal.querySelector('.uc-stories ul');
|
|
646
|
+
usList.innerHTML = '';
|
|
647
|
+
(doc.userStories || []).forEach(us => {
|
|
648
|
+
const li = document.createElement('li');
|
|
649
|
+
li.textContent = `As a ${us.role}, I want ${us.want}, so that ${us.so}`;
|
|
650
|
+
usList.appendChild(li);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Wireframe — safety-checked before insertion
|
|
654
|
+
const wf = modal.querySelector('.uc-wireframe');
|
|
655
|
+
wf.innerHTML = '';
|
|
656
|
+
if (doc.wireframe && wireframeSafe(doc.wireframe)) {
|
|
657
|
+
wf.innerHTML = doc.wireframe;
|
|
658
|
+
wf.hidden = false;
|
|
659
|
+
} else {
|
|
660
|
+
wf.hidden = true;
|
|
661
|
+
if (doc.wireframe) console.warn(`Wireframe for ${id} failed safety check.`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
modal.hidden = false;
|
|
665
|
+
document.body.classList.add('uc-modal-open');
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function closeUcModal() {
|
|
669
|
+
const modal = document.getElementById('uc-modal');
|
|
670
|
+
modal.hidden = true;
|
|
671
|
+
document.body.classList.remove('uc-modal-open');
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Esc closes the modal.
|
|
675
|
+
document.addEventListener('keydown', e => {
|
|
676
|
+
if (e.key === 'Escape') closeUcModal();
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
// Highlight an actor on the diagram for ~2s.
|
|
680
|
+
function pulseActor(actorId) {
|
|
681
|
+
const g = document.getElementById('actor-group-' + actorId);
|
|
682
|
+
if (!g) return;
|
|
683
|
+
g.classList.add('actor-pulse');
|
|
684
|
+
setTimeout(() => g.classList.remove('actor-pulse'), 2000);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Wireframe sanity check — see §WF. Rejects scripts, event handlers, external URLs.
|
|
688
|
+
function wireframeSafe(html) {
|
|
689
|
+
const forbidden = [
|
|
690
|
+
/<script\b/i,
|
|
691
|
+
/\son\w+\s*=/i,
|
|
692
|
+
/<iframe\b/i,
|
|
693
|
+
/src\s*=\s*["']https?:/i,
|
|
694
|
+
/href\s*=\s*["']https?:/i,
|
|
695
|
+
/@import\b/i,
|
|
696
|
+
/<style\b/i
|
|
697
|
+
];
|
|
698
|
+
return !forbidden.some(rx => rx.test(html));
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
461
702
|
UC group: `<g id="uc-uc1" data-uc-id="uc1" onclick="showDesc('uc1','uc',event)" style="cursor:pointer">`
|
|
462
703
|
Actor group: `<g id="actor-student" data-actor-id="student" onclick="showDesc('student','actor',event)" style="cursor:pointer">`
|
|
463
704
|
|
|
705
|
+
### Clickable association lines (required)
|
|
706
|
+
|
|
707
|
+
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.
|
|
708
|
+
|
|
709
|
+
```html
|
|
710
|
+
<!-- Visible line — fixLayout writes x1/y1/x2/y2 here -->
|
|
711
|
+
<line data-conn-from="worker" data-conn-to="uc-receive"
|
|
712
|
+
x1="0" y1="0" x2="0" y2="0"
|
|
713
|
+
stroke="#334155" stroke-width="1.8"/>
|
|
714
|
+
|
|
715
|
+
<!-- Click hitbox — invisible, tracks the same endpoints, carries the id -->
|
|
716
|
+
<line class="conn-hitbox" data-conn-id="a1"
|
|
717
|
+
data-hitbox-from="worker" data-hitbox-to="uc-receive"
|
|
718
|
+
x1="0" y1="0" x2="0" y2="0"
|
|
719
|
+
stroke="transparent" stroke-width="14"
|
|
720
|
+
style="cursor:pointer"
|
|
721
|
+
onclick="showDesc('a1','assoc',event)"/>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
Inside `fixLayout` Step 8, copy the endpoints from each visible line to its matching hitbox so the clickable region tracks the visible line exactly:
|
|
725
|
+
|
|
726
|
+
```javascript
|
|
727
|
+
// After writing x1/y1/x2/y2 on the visible line:
|
|
728
|
+
const hitbox = svg.querySelector(
|
|
729
|
+
`.conn-hitbox[data-hitbox-from="${fId}"][data-hitbox-to="${tId}"]`);
|
|
730
|
+
if (hitbox) {
|
|
731
|
+
hitbox.setAttribute('x1', x1); hitbox.setAttribute('y1', y1);
|
|
732
|
+
hitbox.setAttribute('x2', x2); hitbox.setAttribute('y2', y2);
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
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.
|
|
737
|
+
|
|
738
|
+
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.
|
|
739
|
+
|
|
464
740
|
### Timing labels on cron/time-actor associations (required)
|
|
465
741
|
|
|
466
742
|
When a connection carries a `timing` field (every connection from a system-time actor MUST have one — see §2), inject the label at the line midpoint **after** `fixLayout` has written the endpoints. The helper below is idempotent: it removes any existing `.timing-label` group before re-rendering, so calling it again after another `fixLayout` pass is safe.
|
|
@@ -510,10 +786,104 @@ The `connections` entry for a timed link looks like:
|
|
|
510
786
|
|
|
511
787
|
---
|
|
512
788
|
|
|
789
|
+
## §WF. Wireframe Authoring (Per-UC, Optional)
|
|
790
|
+
|
|
791
|
+
Where a UC benefits from a visual mock of the UI the user sees, 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).
|
|
792
|
+
|
|
793
|
+
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.
|
|
794
|
+
|
|
795
|
+
### Root element
|
|
796
|
+
|
|
797
|
+
```html
|
|
798
|
+
<div class="wf-screen">
|
|
799
|
+
<!-- optional header -->
|
|
800
|
+
<h3 class="wf-heading">כותרת המסך</h3>
|
|
801
|
+
|
|
802
|
+
<!-- form rows -->
|
|
803
|
+
<div class="wf-row">
|
|
804
|
+
<label class="wf-label">שם משתמש</label>
|
|
805
|
+
<input class="wf-input" disabled placeholder="" />
|
|
806
|
+
</div>
|
|
807
|
+
<div class="wf-row">
|
|
808
|
+
<label class="wf-label">סיסמה</label>
|
|
809
|
+
<input class="wf-input" disabled placeholder="" />
|
|
810
|
+
</div>
|
|
811
|
+
|
|
812
|
+
<!-- a table mock -->
|
|
813
|
+
<table class="wf-table">
|
|
814
|
+
<thead><tr><th>פריט</th><th>כמות</th><th>מחיר</th></tr></thead>
|
|
815
|
+
<tbody>
|
|
816
|
+
<tr><td>...</td><td>...</td><td>...</td></tr>
|
|
817
|
+
</tbody>
|
|
818
|
+
</table>
|
|
819
|
+
|
|
820
|
+
<!-- an image placeholder -->
|
|
821
|
+
<div class="wf-image-box">[לוגו / תמונה]</div>
|
|
822
|
+
|
|
823
|
+
<!-- buttons are inert visual placeholders only -->
|
|
824
|
+
<div class="wf-actions">
|
|
825
|
+
<button class="wf-button wf-primary">אישור</button>
|
|
826
|
+
<button class="wf-button">ביטול</button>
|
|
827
|
+
</div>
|
|
828
|
+
|
|
829
|
+
<!-- free-text hints / callouts -->
|
|
830
|
+
<p class="wf-hint">לחיצה על "אישור" שולחת את הטופס ועוברת למסך הסיכום.</p>
|
|
831
|
+
</div>
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### Allowed CSS classes
|
|
835
|
+
|
|
836
|
+
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`.
|
|
837
|
+
|
|
838
|
+
### Hard forbidden patterns
|
|
839
|
+
|
|
840
|
+
The `wireframeSafe()` regex rejects any of the following. A failing wireframe renders a placeholder and logs a warning — it is NEVER injected.
|
|
841
|
+
|
|
842
|
+
| Forbidden | Why |
|
|
843
|
+
|---|---|
|
|
844
|
+
| `<script>` | Can execute arbitrary JS in the host page. |
|
|
845
|
+
| `on*=` attributes (`onclick`, `onerror`, …) | Same. |
|
|
846
|
+
| `<iframe>` | Can load arbitrary content. |
|
|
847
|
+
| `src="http…"` / `href="http…"` | No external fetches — the HTML must remain self-contained. |
|
|
848
|
+
| `@import` | Same reason. |
|
|
849
|
+
| `<style>` block inside the wireframe | Styles come from the skill's `.wf-*` theme only, to keep wireframes visually consistent across UCs. |
|
|
850
|
+
|
|
851
|
+
### Size and scope
|
|
852
|
+
|
|
853
|
+
- 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`.
|
|
854
|
+
- Wireframes are static visual mocks. Form fields use `disabled` so the cursor shows they are illustrative.
|
|
855
|
+
- Buttons are not wired to anything — `onclick` is forbidden. They communicate affordance, not behavior.
|
|
856
|
+
- Hebrew RTL is inherited from `<body dir="rtl">`. Do not override `direction`.
|
|
857
|
+
|
|
858
|
+
### When NOT to include a wireframe
|
|
859
|
+
|
|
860
|
+
- The UC has no user-facing screen (e.g., a back-end cron job, an automated reconciliation).
|
|
861
|
+
- The UI is trivial (e.g., a single "Approve" confirmation prompt) — prose in the scenario flow is enough.
|
|
862
|
+
- You cannot author the wireframe without making up details the spec does not support — put those in `diagramModel.openQuestions` instead.
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
513
866
|
## 9. diagramModel (mandatory)
|
|
514
867
|
|
|
515
868
|
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.
|
|
516
869
|
|
|
870
|
+
**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.
|
|
871
|
+
|
|
872
|
+
### Assumptions and open questions
|
|
873
|
+
|
|
874
|
+
Both `diagramModel` and `diagramLayout` carry two parallel string arrays, matching the BPMN skill's shape:
|
|
875
|
+
|
|
876
|
+
```javascript
|
|
877
|
+
{
|
|
878
|
+
// ...
|
|
879
|
+
assumptions: [ 'המערכת מזהה כל עובד מחסן דרך מזהה משתמש יחיד.',
|
|
880
|
+
'ייבוא נתונים מה-ERP של הספק מתבצע באמצעות API ולא קובץ.' ],
|
|
881
|
+
openQuestions: [ 'האם נדרש אישור נוסף על ידי מנהל לכל שינוי רמות מלאי?' ]
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
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.
|
|
886
|
+
|
|
517
887
|
---
|
|
518
888
|
|
|
519
889
|
## 10. Styling
|
|
@@ -529,9 +899,114 @@ Association line: #334155, 1.8px, no arrowhead
|
|
|
529
899
|
Include/extend: #7c3aed, 1.6px, dasharray 6 4, open arrowhead, label 9.5px
|
|
530
900
|
Generalization: #334155, 1.8px, hollow triangle (fill white)
|
|
531
901
|
System title: white on #1e40af, 14px bold
|
|
902
|
+
Assumptions panel: white bg, border #cbd5e1 (1px), rx=8, padding 16px, margin-top 20px
|
|
903
|
+
Assumptions h3: #1e40af, 13px, weight 600, margin-bottom 6px
|
|
904
|
+
Assumptions li: #334155, 12px, line-height 1.6, list-style disc inside
|
|
532
905
|
Fonts: 'Noto Sans Hebrew' + 'IBM Plex Mono' from Google Fonts
|
|
533
906
|
```
|
|
534
907
|
|
|
908
|
+
The assumptions panel CSS must be included in the HTML `<style>` block:
|
|
909
|
+
|
|
910
|
+
```css
|
|
911
|
+
.assumptions-panel { display: flex; gap: 24px; max-width: 1000px;
|
|
912
|
+
margin: 20px auto; padding: 16px 20px; background: #fff;
|
|
913
|
+
border: 1px solid #cbd5e1; border-radius: 8px; direction: rtl; }
|
|
914
|
+
.assumptions-col { flex: 1; min-width: 0; }
|
|
915
|
+
.assumptions-col h3 { margin: 0 0 6px; font-size: 13px; font-weight: 600;
|
|
916
|
+
color: #1e40af; }
|
|
917
|
+
.assumptions-col ul { margin: 0; padding-right: 18px; }
|
|
918
|
+
.assumptions-col li { color: #334155; font-size: 12px; line-height: 1.6; }
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
### v2 — UC modal styling
|
|
922
|
+
|
|
923
|
+
```css
|
|
924
|
+
/* Lock body scroll while the modal is open */
|
|
925
|
+
body.uc-modal-open { overflow: hidden; }
|
|
926
|
+
|
|
927
|
+
.uc-modal { position: fixed; inset: 0; z-index: 2000; display: flex;
|
|
928
|
+
align-items: flex-start; justify-content: center; padding: 40px 20px;
|
|
929
|
+
overflow-y: auto; direction: rtl;
|
|
930
|
+
font-family: 'Noto Sans Hebrew', sans-serif; }
|
|
931
|
+
.uc-modal-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.55); }
|
|
932
|
+
.uc-modal-body { position: relative; z-index: 1; width: 100%; max-width: 960px;
|
|
933
|
+
background: #fff; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,.25);
|
|
934
|
+
padding: 28px 32px 32px; color: #1e293b; }
|
|
935
|
+
.uc-modal-close { position: absolute; top: 10px; left: 14px; background: none;
|
|
936
|
+
border: none; font-size: 24px; color: #64748b; cursor: pointer; }
|
|
937
|
+
.uc-modal-body header { border-bottom: 1px solid #e2e8f0; padding-bottom: 12px;
|
|
938
|
+
margin-bottom: 18px; }
|
|
939
|
+
.uc-modal-body .uc-id { display: inline-block; font-family: 'IBM Plex Mono',monospace;
|
|
940
|
+
font-size: 11px; color: #64748b; background: #f1f5f9; padding: 2px 8px;
|
|
941
|
+
border-radius: 4px; margin-bottom: 6px; }
|
|
942
|
+
.uc-modal-body .uc-name { margin: 0 0 4px; font-size: 20px; color: #1e40af; font-weight: 700; }
|
|
943
|
+
.uc-modal-body .uc-primary { font-size: 13px; color: #334155; }
|
|
944
|
+
.uc-modal-body .uc-primary-label { color: #64748b; margin-left: 6px; }
|
|
945
|
+
.uc-modal-body .uc-primary-link { color: #1e40af; cursor: pointer; font-weight: 600;
|
|
946
|
+
text-decoration: underline dotted; }
|
|
947
|
+
.uc-description { margin-bottom: 20px; font-size: 14px; line-height: 1.7; color: #334155; }
|
|
948
|
+
|
|
949
|
+
.uc-scenarios h3 { font-size: 14px; color: #1e40af; margin: 18px 0 6px;
|
|
950
|
+
border-right: 3px solid #3b82f6; padding-right: 10px; }
|
|
951
|
+
.uc-scenarios ol { margin: 0 0 14px; padding-right: 22px; }
|
|
952
|
+
.uc-scenarios ol li { color: #1e293b; font-size: 13px; line-height: 1.9; }
|
|
953
|
+
.step-actor { color: #1e40af; font-weight: 600; cursor: pointer; text-decoration: underline dotted; }
|
|
954
|
+
.step-system { color: #475569; font-style: italic; }
|
|
955
|
+
.step-include { color: #7c3aed; font-family: 'IBM Plex Mono', monospace; font-size: 11px; }
|
|
956
|
+
.step-uc-ref { color: #7c3aed; cursor: pointer; text-decoration: underline; }
|
|
957
|
+
.ext-inline, .ext-sub { list-style: none; color: #64748b; font-size: 12px;
|
|
958
|
+
padding-right: 24px; line-height: 1.7; }
|
|
959
|
+
|
|
960
|
+
.uc-prepostuser { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 18px;
|
|
961
|
+
margin: 18px 0 20px; }
|
|
962
|
+
.uc-prepostuser > div { background: #f8fafc; border: 1px solid #e2e8f0;
|
|
963
|
+
border-radius: 8px; padding: 12px 14px; }
|
|
964
|
+
.uc-prepostuser h4 { margin: 0 0 6px; font-size: 12px; color: #1e40af; font-weight: 600; }
|
|
965
|
+
.uc-prepostuser ul { margin: 0; padding-right: 18px; font-size: 12px;
|
|
966
|
+
color: #334155; line-height: 1.7; }
|
|
967
|
+
|
|
968
|
+
.uc-wireframe { border-top: 1px dashed #cbd5e1; padding-top: 16px; margin-top: 6px; }
|
|
969
|
+
.uc-wireframe:empty, .uc-wireframe[hidden] { display: none; }
|
|
970
|
+
|
|
971
|
+
/* Actor pulse animation used by pulseActor() */
|
|
972
|
+
.actor-pulse { animation: actor-pulse 0.6s ease-in-out 3; }
|
|
973
|
+
@keyframes actor-pulse {
|
|
974
|
+
0%, 100% { filter: none; }
|
|
975
|
+
50% { filter: drop-shadow(0 0 8px #3b82f6); }
|
|
976
|
+
}
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
### v2 — Wireframe scoped theme
|
|
980
|
+
|
|
981
|
+
```css
|
|
982
|
+
.wf-screen { background: #f8fafc; border: 1px solid #cbd5e1; border-radius: 8px;
|
|
983
|
+
padding: 18px 20px; font-family: 'Noto Sans Hebrew', sans-serif;
|
|
984
|
+
color: #1e293b; direction: rtl; max-width: 760px; margin: 0 auto; }
|
|
985
|
+
.wf-heading { margin: 0 0 14px; font-size: 15px; color: #1e40af; font-weight: 700;
|
|
986
|
+
border-bottom: 1px solid #e2e8f0; padding-bottom: 6px; }
|
|
987
|
+
.wf-section { margin: 14px 0; }
|
|
988
|
+
.wf-row { display: flex; gap: 10px; align-items: center; margin: 6px 0; }
|
|
989
|
+
.wf-label { font-size: 12px; color: #475569; min-width: 120px; }
|
|
990
|
+
.wf-input, .wf-textarea, .wf-select { flex: 1; background: #fff;
|
|
991
|
+
border: 1px solid #cbd5e1; border-radius: 4px; padding: 6px 8px;
|
|
992
|
+
font-size: 12px; color: #334155; }
|
|
993
|
+
.wf-textarea { min-height: 60px; }
|
|
994
|
+
.wf-table { width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 12px; }
|
|
995
|
+
.wf-table th, .wf-table td { border: 1px solid #cbd5e1; padding: 6px 8px;
|
|
996
|
+
text-align: right; }
|
|
997
|
+
.wf-table th { background: #e2e8f0; color: #1e40af; font-weight: 600; }
|
|
998
|
+
.wf-actions { display: flex; gap: 8px; margin-top: 12px; justify-content: flex-start; }
|
|
999
|
+
.wf-button { background: #fff; border: 1px solid #64748b; border-radius: 4px;
|
|
1000
|
+
padding: 6px 14px; font-size: 12px; color: #334155; cursor: default; }
|
|
1001
|
+
.wf-primary { background: #1e40af; color: #fff; border-color: #1e40af; }
|
|
1002
|
+
.wf-image-box { border: 1px dashed #94a3b8; background: #f1f5f9; color: #64748b;
|
|
1003
|
+
text-align: center; padding: 24px; border-radius: 4px; font-size: 12px;
|
|
1004
|
+
margin: 10px 0; }
|
|
1005
|
+
.wf-hint { font-size: 11px; color: #64748b; font-style: italic; margin: 6px 0; }
|
|
1006
|
+
.wf-badge { display: inline-block; background: #e0f2fe; color: #0369a1;
|
|
1007
|
+
padding: 2px 8px; border-radius: 999px; font-size: 10px; font-weight: 600; }
|
|
1008
|
+
```
|
|
1009
|
+
|
|
535
1010
|
---
|
|
536
1011
|
|
|
537
1012
|
## 11. HTML File Structure
|
|
@@ -571,13 +1046,50 @@ Fonts: 'Noto Sans Hebrew' + 'IBM Plex Mono' from Google Fonts
|
|
|
571
1046
|
<svg id="diagram-svg-1" viewBox="0 0 1100 900" xmlns="http://www.w3.org/2000/svg">...</svg>
|
|
572
1047
|
</div>
|
|
573
1048
|
|
|
1049
|
+
<!-- Assumptions / open-questions panel (render only if at least one non-empty) -->
|
|
1050
|
+
<div id="assumptions-panel" class="assumptions-panel" hidden>
|
|
1051
|
+
<div class="assumptions-col" id="assumptions-col">
|
|
1052
|
+
<h3>הנחות</h3>
|
|
1053
|
+
<ul id="assumptions-list"></ul>
|
|
1054
|
+
</div>
|
|
1055
|
+
<div class="assumptions-col" id="open-questions-col">
|
|
1056
|
+
<h3>שאלות פתוחות</h3>
|
|
1057
|
+
<ul id="open-questions-list"></ul>
|
|
1058
|
+
</div>
|
|
1059
|
+
</div>
|
|
1060
|
+
|
|
1061
|
+
<!-- v2 — UC modal (populated by openUcModal; hidden by default) -->
|
|
1062
|
+
<div id="uc-modal" class="uc-modal" hidden role="dialog" aria-modal="true">
|
|
1063
|
+
<div class="uc-modal-backdrop" onclick="closeUcModal()"></div>
|
|
1064
|
+
<div class="uc-modal-body">
|
|
1065
|
+
<button class="uc-modal-close" onclick="closeUcModal()" aria-label="Close">×</button>
|
|
1066
|
+
<header>
|
|
1067
|
+
<span class="uc-id"></span>
|
|
1068
|
+
<h2 class="uc-name"></h2>
|
|
1069
|
+
<div class="uc-primary">
|
|
1070
|
+
<span class="uc-primary-label">שחקן ראשי:</span>
|
|
1071
|
+
<a class="uc-primary-link"></a>
|
|
1072
|
+
</div>
|
|
1073
|
+
</header>
|
|
1074
|
+
<section class="uc-description"></section>
|
|
1075
|
+
<section class="uc-scenarios"></section>
|
|
1076
|
+
<section class="uc-prepostuser">
|
|
1077
|
+
<div class="uc-pre"><h4>תנאים מוקדמים</h4><ul></ul></div>
|
|
1078
|
+
<div class="uc-post"><h4>תנאים סופיים</h4><ul></ul></div>
|
|
1079
|
+
<div class="uc-stories"><h4>סיפורי משתמש</h4><ul></ul></div>
|
|
1080
|
+
</section>
|
|
1081
|
+
<section class="uc-wireframe" hidden></section>
|
|
1082
|
+
</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
|
|
574
1085
|
<!-- VP export button -->
|
|
575
1086
|
<button class="vp-export-btn" onclick="exportXMI()">Download .xmi (Visual Paradigm)</button>
|
|
576
1087
|
|
|
577
1088
|
<script>
|
|
578
1089
|
const diagramLayouts = [ /* one layout object per tab, per §6 */ ];
|
|
579
|
-
const
|
|
580
|
-
const
|
|
1090
|
+
const actorDescriptions = { /* §8b */ };
|
|
1091
|
+
const assocDescriptions = { /* §8b */ };
|
|
1092
|
+
const useCaseDocs = { /* §8a — full v2 per-UC documentation */ };
|
|
581
1093
|
const diagramModel = { /* model-shape.md, kind: 'use-case' */ };
|
|
582
1094
|
|
|
583
1095
|
let activeTab = 0;
|
|
@@ -589,14 +1101,42 @@ Fonts: 'Noto Sans Hebrew' + 'IBM Plex Mono' from Google Fonts
|
|
|
589
1101
|
fixLayout(diagramLayouts[index]); // idempotent
|
|
590
1102
|
}
|
|
591
1103
|
|
|
592
|
-
function fixLayout(layout)
|
|
593
|
-
function showDesc(id, type, event)
|
|
594
|
-
function
|
|
595
|
-
function
|
|
596
|
-
function
|
|
1104
|
+
function fixLayout(layout) { /* §6 */ }
|
|
1105
|
+
function showDesc(id, type, event){ /* §8: routes to openUcModal for UCs, showPopup otherwise */ }
|
|
1106
|
+
function showPopup(id, type, event){ /* §8b */ }
|
|
1107
|
+
function hideDesc() { /* §8b */ }
|
|
1108
|
+
function openUcModal(id) { /* §8a */ }
|
|
1109
|
+
function closeUcModal() { /* §8a */ }
|
|
1110
|
+
function pulseActor(actorId) { /* §8a */ }
|
|
1111
|
+
function wireframeSafe(html) { /* §8a / §WF */ }
|
|
1112
|
+
function xmiBody(elements, rels) { /* export-buttons.md — v2 emits preconditions/postconditions/ownedBehavior */ }
|
|
1113
|
+
function exportXMI() { /* export-buttons.md */ }
|
|
1114
|
+
|
|
1115
|
+
function renderAssumptions() {
|
|
1116
|
+
const a = (diagramModel && diagramModel.assumptions) || [];
|
|
1117
|
+
const q = (diagramModel && diagramModel.openQuestions) || [];
|
|
1118
|
+
const panel = document.getElementById('assumptions-panel');
|
|
1119
|
+
if (!panel) return;
|
|
1120
|
+
if (!a.length && !q.length) { panel.hidden = true; return; }
|
|
1121
|
+
panel.hidden = false;
|
|
1122
|
+
const ul = id => document.getElementById(id);
|
|
1123
|
+
const fill = (listEl, items) => {
|
|
1124
|
+
listEl.innerHTML = '';
|
|
1125
|
+
items.forEach(s => {
|
|
1126
|
+
const li = document.createElement('li');
|
|
1127
|
+
li.textContent = s;
|
|
1128
|
+
listEl.appendChild(li);
|
|
1129
|
+
});
|
|
1130
|
+
};
|
|
1131
|
+
fill(ul('assumptions-list'), a);
|
|
1132
|
+
fill(ul('open-questions-list'), q);
|
|
1133
|
+
document.getElementById('assumptions-col').hidden = !a.length;
|
|
1134
|
+
document.getElementById('open-questions-col').hidden = !q.length;
|
|
1135
|
+
}
|
|
597
1136
|
|
|
598
1137
|
document.addEventListener('DOMContentLoaded', () => {
|
|
599
1138
|
diagramLayouts.forEach(l => fixLayout(l));
|
|
1139
|
+
renderAssumptions();
|
|
600
1140
|
});
|
|
601
1141
|
</script>
|
|
602
1142
|
</body>
|
|
@@ -616,6 +1156,14 @@ Fonts: 'Noto Sans Hebrew' + 'IBM Plex Mono' from Google Fonts
|
|
|
616
1156
|
|
|
617
1157
|
## 13. Pre-Delivery Checklist
|
|
618
1158
|
|
|
1159
|
+
- [ ] 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`.
|
|
1160
|
+
- [ ] Every `primaryActor` id exists in `actors[]` AND has an association with `role: 'initiator'` on that UC (aligning §4.1 with §4.4).
|
|
1161
|
+
- [ ] 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.
|
|
1162
|
+
- [ ] 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.
|
|
1163
|
+
- [ ] Any `wireframe` HTML passes `wireframeSafe()` (no `<script>`, no `on*=`, no `<iframe>`, no external URLs, no `@import`, no inline `<style>`); all classes are `wf-*` only.
|
|
1164
|
+
- [ ] Every actor-UC association has a written §4.1 justification block (role, initiates? one-line "why"); every UC has ≥1 association with `initiates = yes`.
|
|
1165
|
+
- [ ] 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.
|
|
1166
|
+
- [ ] `assumptions` and `openQuestions` arrays are present in `diagramModel`; the HTML renders the bottom panel whenever either is non-empty.
|
|
619
1167
|
- [ ] Every «include» and «extend» has a written §4 justification block; any that cannot be filled in has been deleted.
|
|
620
1168
|
- [ ] No «include» with exactly one incoming arrow — every include target has ≥2 bases AND zero actor associations.
|
|
621
1169
|
- [ ] No generic "ניהול הגדרות מערכת" or combined-entity UCs; each entity has its own UC.
|