sad-mcp 1.1.8 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sad-mcp",
3
- "version": "1.1.8",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for Software Analysis and Design course materials at BGU",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- lines.push(` <packagedElement xmi:type="uml:UseCase" xmi:id="${el.id}" name="${xmlEscape(el.name)}"/>`);
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, description: '',
74
- x, y, rx, ry } // rx/ry = ellipse radii
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:
@@ -143,6 +143,49 @@ Arrow: dashed, open arrowhead, `Base UC → Included UC`.
143
143
 
144
144
  Arrow: dashed, open arrowhead, `Extension UC → Base UC`. Use at most once or twice per diagram.
145
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
+
146
189
  ### Quick disqualifiers
147
190
 
148
191
  - Exactly one incoming «include» arrow → fold into the parent.
@@ -432,15 +475,26 @@ If neither trigger fires, produce a single tab. Do not split on gut feeling.
432
475
 
433
476
  ---
434
477
 
435
- ## 8. Clickable Descriptions for UCs, Actors, and Associations (Always Include)
478
+ ## 8. Clickable Descriptions — Modal for UCs, Popups for Actors & Associations
436
479
 
437
- Every UC ellipse, every actor, **and every association line** must be clickable — click shows a floating panel with name + description. One `showDesc(id, type, event)` function handles all three.
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
438
496
 
439
497
  ```javascript
440
- const useCaseDescriptions = {
441
- 'uc1': { name: 'פתיחת הזמנה', desc: 'המשתמש פותח הזמנה חדשה ומציין פרטי המוצרים הנדרשים.' },
442
- // ... one entry per UC
443
- };
444
498
  const actorDescriptions = {
445
499
  'customer': { name: 'לקוח', desc: 'לקוח הרשום במערכת. יוזם הזמנות ועוקב אחר סטטוס.' },
446
500
  // ... one entry per actor
@@ -451,10 +505,9 @@ const assocDescriptions = {
451
505
  // ... one entry per association line you want described
452
506
  };
453
507
 
454
- function showDesc(id, type, event) {
508
+ function showPopup(id, type, event) {
455
509
  hideDesc();
456
- const data = type === 'uc' ? useCaseDescriptions[id]
457
- : type === 'actor' ? actorDescriptions[id]
510
+ const data = type === 'actor' ? actorDescriptions[id]
458
511
  : type === 'assoc' ? assocDescriptions[id]
459
512
  : null;
460
513
  if (!data) return;
@@ -486,6 +539,166 @@ document.addEventListener('click', e => {
486
539
  });
487
540
  ```
488
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
+
489
702
  UC group: `<g id="uc-uc1" data-uc-id="uc1" onclick="showDesc('uc1','uc',event)" style="cursor:pointer">`
490
703
  Actor group: `<g id="actor-student" data-actor-id="student" onclick="showDesc('student','actor',event)" style="cursor:pointer">`
491
704
 
@@ -573,10 +786,89 @@ The `connections` entry for a timed link looks like:
573
786
 
574
787
  ---
575
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
+
576
866
  ## 9. diagramModel (mandatory)
577
867
 
578
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.
579
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
+
580
872
  ### Assumptions and open questions
581
873
 
582
874
  Both `diagramModel` and `diagramLayout` carry two parallel string arrays, matching the BPMN skill's shape:
@@ -626,6 +918,95 @@ The assumptions panel CSS must be included in the HTML `<style>` block:
626
918
  .assumptions-col li { color: #334155; font-size: 12px; line-height: 1.6; }
627
919
  ```
628
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
+
629
1010
  ---
630
1011
 
631
1012
  ## 11. HTML File Structure
@@ -677,13 +1058,38 @@ The assumptions panel CSS must be included in the HTML `<style>` block:
677
1058
  </div>
678
1059
  </div>
679
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
+
680
1085
  <!-- VP export button -->
681
1086
  <button class="vp-export-btn" onclick="exportXMI()">Download .xmi (Visual Paradigm)</button>
682
1087
 
683
1088
  <script>
684
1089
  const diagramLayouts = [ /* one layout object per tab, per §6 */ ];
685
- const useCaseDescriptions = { /* §8 */ };
686
- const actorDescriptions = { /* §8 */ };
1090
+ const actorDescriptions = { /* §8b */ };
1091
+ const assocDescriptions = { /* §8b */ };
1092
+ const useCaseDocs = { /* §8a — full v2 per-UC documentation */ };
687
1093
  const diagramModel = { /* model-shape.md, kind: 'use-case' */ };
688
1094
 
689
1095
  let activeTab = 0;
@@ -695,11 +1101,16 @@ The assumptions panel CSS must be included in the HTML `<style>` block:
695
1101
  fixLayout(diagramLayouts[index]); // idempotent
696
1102
  }
697
1103
 
698
- function fixLayout(layout) { /* §6 */ }
699
- function showDesc(id, type, event) { /* §8 */ }
700
- function hideDesc() { /* §8 */ }
701
- function xmiBody(elements, rels) { /* export-buttons.md */ }
702
- function exportXMI() { /* export-buttons.md */ }
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 */ }
703
1114
 
704
1115
  function renderAssumptions() {
705
1116
  const a = (diagramModel && diagramModel.assumptions) || [];
@@ -745,6 +1156,11 @@ The assumptions panel CSS must be included in the HTML `<style>` block:
745
1156
 
746
1157
  ## 13. Pre-Delivery Checklist
747
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.
748
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`.
749
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.
750
1166
  - [ ] `assumptions` and `openQuestions` arrays are present in `diagramModel`; the HTML renders the bottom panel whenever either is non-empty.