# Round 04 — Maariv-Press SSE → A2UI Mapping (43 events)

**Date**: 2026-04-30
**Time**: 45 min (of 3h)
**Status**: ✅ COMPLETE — full mapping table
**Round Type**: Critical Path — connects SSE backend to UI runtime

---

## 🎯 שאלה מרכזית

**איך 43 ה-events של press_v3 SSE הופכים ל-A2UI v0.9 messages?**

---

## 🏆 התוצאה: 9 Phases × 43 Events → 4 A2UI Message Types

ה-43 events נתפסים על-ידי 4 הסוגים של A2UI v0.9: `createSurface`, `updateComponents`, `updateDataModel`, `deleteSurface`.

ה-mapping הוא **deterministic** — לכל event יש מקור A2UI יחיד ידוע מראש.

---

## 📊 הטבלה המלאה — 43 Events Mapping

### Phase 1 · Pipeline Lifecycle (5 events)

| SSE Event | A2UI Message | Surface | Action |
|---|---|---|---|
| `pipeline_start` | `createSurface` | press-flow-main | יצירת ה-surface הראשי + theme.tenantId |
| `pipeline_warmup` | `updateDataModel` | press-flow-main | path: `/meta/timing` |
| `pipeline_phase` | `updateComponents` | press-flow-main | עדכון stepper component (active step) |
| `pipeline_checkpoint` | `updateDataModel` | press-flow-main | path: `/meta/checkpoints[]` |
| `pipeline_end` | `updateDataModel` | press-flow-main | path: `/meta/status: "done"` |

### Phase 2 · Input Analysis (4 events)

| SSE Event | A2UI Message | Path / Component |
|---|---|---|
| `input_language` | `updateDataModel` | `/input/language` |
| `input_length` | `updateDataModel` | `/input/length` (chars) |
| `input_sentiment` | `updateDataModel` | `/input/sentiment` |
| `input_entities` | `updateComponents` | entity-chips component (List children template) |

### Phase 3 · Classification (5 events)

| SSE Event | A2UI Message | Component / Path |
|---|---|---|
| `classify_start` | `updateDataModel` | `/classify/state: "running"` |
| `classify_desk` | `updateComponents` | desk-badge (color = `--desk-{name}`) |
| `classify_urgency` | `updateComponents` | urgency-indicator (breaking → red flash) |
| `classify_sensitivity` | `updateDataModel` | `/classify/sensitivity` |
| `classify_done` | `updateDataModel` | `/classify/state: "done"` + `/classify/result` |

### Phase 4 · 5W Extraction (5 events) — **STREAMING**

| SSE Event | A2UI Message | Behavior |
|---|---|---|
| `5w_extracting` | `updateDataModel` | `/w5/state: "extracting"` |
| **`5w_partial`** | `updateDataModel` | path: `/w5/{field}` — **streaming field-by-field** (Who / What / When / Where / Why מתעדכנים בזמן אמת) |
| `5w_validate` | `updateComponents` | w5-bar (validation state per field) |
| `5w_enrich` | `updateDataModel` | path: `/w5/enrichments` |
| `5w_done` | `updateDataModel` | `/w5/state: "done"` |

**זה ה-event הכי מרגש**. ה-5w_partial מגיע 5 פעמים, פעם לכל field. ב-A2UI זה מתורגם ל-5 `updateDataModel` rapid-fire. ה-w5-bar component יודע לרנדר עם typewriter effect כי הוא קורא `{path: "/w5/who"}` שמתעדכן per chunk.

### Phase 5 · Context Search (5 events)

| SSE Event | A2UI Message | Component |
|---|---|---|
| `context_searching` | `updateDataModel` | `/contexts/state: "searching"` |
| `context_match` | `updateComponents` | context-grid (template-based List, child appended per match) |
| `radio_context` | `updateComponents` | context-card (component: "RadioContext") |
| `social_context` | `updateComponents` | context-card (component: "SocialContext") |
| `context_done` | `updateDataModel` | `/contexts/total: count` |

### Phase 6 · Writing (4 events) — **STREAMING**

| SSE Event | A2UI Message | Component / Path |
|---|---|---|
| `writing_start` | `createSurface` | article-output (חדש — surface שלם משלו!) |
| `writing_outline` | `updateComponents` | article-outline (Column children = sections) |
| **`chunk`** | `updateDataModel` | path: `/article/text` — **token-by-token streaming** |
| `writing_done` | `updateDataModel` | `/article/state: "complete"` |

הסבר: ה-writing מקבל **surface משלו** (article-output). זה כי ה-press flow surface הראשי לא רוצה rerender על כל token. surface נפרד = re-render isolated. UX חלק.

### Phase 7 · Quality Checks (5 events)

| SSE Event | A2UI Message | Component |
|---|---|---|
| `factcheck` | `updateComponents` | fact-check-flag (per claim) |
| `brand_voice_check` | `updateComponents` | brand-voice-indicator |
| `plagiarism_check` | `updateComponents` | plagiarism-result |
| `edit_pass` | `updateDataModel` | `/quality/edits[]` |
| `readability_score` | `updateComponents` | readability-meter (0-100 gauge) |

### Phase 8 · Output Enrichment (5 events)

| SSE Event | A2UI Message | Path |
|---|---|---|
| `citation_inserted` | `updateComponents` | citation-list (template) |
| `seo_meta_done` | `updateDataModel` | `/seo/meta` |
| `jsonld_done` | `updateDataModel` | `/seo/jsonld` |
| `hreflang_done` | `updateDataModel` | `/seo/hreflang` |
| `ads_targeting_done` | `updateDataModel` | `/ads/targeting` |

### Phase 9 · Final Surfacing (5 events)

| SSE Event | A2UI Message | Action |
|---|---|---|
| `recommendations_done` | `createSurface` | recommendations-panel (chips) |
| `preview_urls` | `updateComponents` | preview-links (Twitter, FB, mobile) |
| `envelope_ready` | `updateDataModel` | `/envelope` (full Jason envelope ready) |
| `article_done` | `updateComponents` | publish-button (enabled state) |
| `error` | `updateDataModel` | `/error` (path always at root) |

---

## 🎬 דוגמה רצף מלא — 1 הודעת דוברות → A2UI stream

```jsonl
{"version":"v0.9","createSurface":{"surfaceId":"press-flow-main","catalogId":"https://master-jason.clastop.app/catalog/press/v1","theme":{"tenantId":"maariv","mode":"light"}}}
{"version":"v0.9","updateComponents":{"surfaceId":"press-flow-main","components":[
  {"id":"root","component":"Column","children":["topbar","stepper","w5bar","main"]},
  {"id":"topbar","component":"DeskAITopbar","brand":"Maariv","role":"editor"},
  {"id":"stepper","component":"PressFlowStepper","steps":["paste","classify","contexts","write","review","publish"],"active":0},
  {"id":"w5bar","component":"FiveWBar","fields":["who","what","when","where","why"]},
  {"id":"main","component":"PasteArea","value":{"path":"/draft/text"}}
]}}

// User pastes text → POST /press/v3/stream

// Stream starts:
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/meta/state","value":"running"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/input/language","value":"he"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/input/length","value":1247}}

// Streaming 5W:
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/w5/who","value":"משרד הביטחון"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/w5/what","value":"עסקת רכש 12 סוללות כיפת ברזל"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/w5/when","value":"2026-04-30T22:00:00Z"}}

// Context cards appearing:
{"version":"v0.9","updateComponents":{"surfaceId":"press-flow-main","components":[
  {"id":"ctx-1","component":"ContextCard","mode":"compact","title":"כיפת ברזל - היסטוריה","score":0.92}
]}}
{"version":"v0.9","updateComponents":{"surfaceId":"press-flow-main","components":[
  {"id":"ctx-2","component":"ContextCard","mode":"compact","title":"מערכת ביטחון - תקציב","score":0.87}
]}}

// New surface for article:
{"version":"v0.9","createSurface":{"surfaceId":"article-output","catalogId":"https://master-jason.clastop.app/catalog/article/v1","theme":{"tenantId":"maariv"}}}

// Streaming article tokens:
{"version":"v0.9","updateDataModel":{"surfaceId":"article-output","path":"/article/text","value":"אמש,"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"article-output","path":"/article/text","value":"אמש, בשעות"}}
{"version":"v0.9","updateDataModel":{"surfaceId":"article-output","path":"/article/text","value":"אמש, בשעות הערב,"}}
// ... 1000+ chunks

{"version":"v0.9","updateDataModel":{"surfaceId":"press-flow-main","path":"/meta/state","value":"done"}}
```

זה הזרם המלא. **3 surfaces, ~50 messages, 2-6 שניות.** ה-UI מתעדכן incrementally — המשתמש רואה הכל קורה בזמן אמת.

---

## 🧠 ההכרעות הטכניות הקריטיות

### 1. למה כל phase מקבל phase_state ב-`/meta/state`?

הUI יודע מה הphase הנוכחי דרך **path בודד** (`/meta/state`). ה-stepper component מקבל `{path: "/meta/state"}` ויודע לאיזה צעד להאיר. ה-LLM לא צריך לחשוב על stepper — הוא רק שולח state.

### 2. למה article יושב ב-surface נפרד?

3 סיבות:
- **Performance**: re-render מבודד. כל chunk של article לא מפיל את ה-press-flow-main surface.
- **Routing**: User יכול לפתוח article-output ב-tab נפרד / drawer. surface = canvas independent.
- **Reusability**: same article-output catalog יכול להשתמש על-ידי AppleNews, RSS, etc.

### 3. למה context_match → updateComponents ולא updateDataModel?

context cards הם **כמות משתנה** (12 cards אופייני, אבל יכול 3 או 50). A2UI v0.9 מטפל בזה דרך **template children**:

```json
{
  "id": "context-grid",
  "component": "ContextGrid",
  "children": {
    "template": "ctx-card-template",
    "data": "/contexts/items"
  }
}
```

כל `context_match` event מוסיף item ל-`/contexts/items` — ה-template מחולל child אוטומטית. אין צורך לעדכן components, רק data.

**Wait** — בעצם זה אומר שגם `context_match` יכול להיות `updateDataModel` ואז ה-template יחולל את הcards. זה אופטימיזציה ל-Round 5 (Catalog).

### 4. Error handling

`error` event → `updateDataModel` ל-path `/error`. ה-error-boundary component (חלק מה-catalog) קורא מ-`{path: "/error"}` ומציג toast/banner.

---

## 📈 Performance Implications

לכל press release flow:
- **~50 SSE events** · 50 A2UI messages
- **~500 bytes per message** average · **~25KB total payload**
- **3 surfaces** · 60-80 components
- **Zero React re-renders של main surface ב-writing phase** (כי article-output הוא surface separate)
- **Estimated render time**: < 200ms initial · < 16ms per chunk update

מול CSS תמיד נטען (~80KB cached infinitely).

---

## 🔌 איך ה-clastop-mega plugin engine נכנס

עכשיו הdot מתחבר. ה-`deskai_press_studio` plugin (שהיה כבר בתכנון) הופך ל-**A2UI bridge**:

```python
# plugin: deskai_press_studio
# capabilities_required: ["http:fetch", "event:emit", "a2ui:emit"]

@on_hook("on_press_flow_created")
async def handle_press_flow(ctx, event):
    # 1. Open A2UI surface
    await ctx.a2ui.create_surface("press-flow-main", catalog="press/v1", theme={"tenantId": ctx.tenant})

    # 2. Stream from maariv-press-api 8105 → translate → emit A2UI
    async for sse_event in ctx.http.stream_sse("http://127.0.0.1:8105/press/v3/stream", body=event.text):
        a2ui_msg = SSE_TO_A2UI_MAP[sse_event.type](sse_event.data)
        await ctx.a2ui.emit(a2ui_msg)

    # 3. Final
    await ctx.events.emit("on_article_publish", envelope=ctx.state.envelope)
```

ה-plugin הוא ה-**translator**. ה-frontend לא צריך לדעת על port 8105. הוא רואה רק A2UI.

---

## ✅ Closure

- [x] 43 events ממופים בטבלה
- [x] רצף שלם מודגם (~50 messages)
- [x] surfaces strategy (multi-surface) מוצדקת
- [x] Performance estimated
- [x] Plugin bridge architecture defined

✅ **Round 04 closed.**

---

## 🛣️ Next: Round 05 — Minimal Pro → A2UI Catalog

איך הופכים את ה-60+ Minimal Pro components (Card, Drawer, Stack, Iconify, Label, Chip, ...) ל-A2UI Catalog Definition Schema?
