---
id: "0152"
title: "matrix-client-pc mini-webapps embebidas: Matrix Widget API v2"
status: pendiente
priority: alta
created: 2026-05-24
related_flows: ["0010"]
related_issues: ["0151", "0153"]
dependencies: ["0151"]
tags: [matrix, widgets, webapps, iframe, sandbox, agents, postmessage]
---

## Objetivo

Implementar host de widgets segun Matrix Widget API v2 (MSC2762, MSC2871, MSC2974). Cada room puede tener widgets activos publicados como state events `m.widget`. Los widgets son URLs cargadas en iframes sandboxed con bridge postMessage que da capabilities controladas (leer eventos del room, enviar eventos, mostrar UI overlay, etc.). Agentes de `agents_and_robots` pueden publicar widgets en sus rooms (ej. dashboard telemetria, formulario, kanban inline, panel de control del agente).

## Tareas

1. Backend Go:
   - `MatrixService.ListWidgets(roomID) -> []Widget` — lee state events `m.widget` del room.
   - `MatrixService.AddWidget(roomID, widget Widget)` — publica state event.
   - `MatrixService.RemoveWidget(roomID, widgetID)`.
   - `MatrixService.GenerateWidgetURL(widget Widget, userID) -> string` — substituye `$matrix_user_id`, `$matrix_room_id`, `$matrix_display_name`, `$matrix_avatar_url`, `$matrix_widget_id`, `$theme` en la URL del widget.
   - Slash command `/widget <url>` handler en composer (issue 0149) que crea state event con widget temporal.
   - `MatrixService.MintWidgetScopedToken(widgetID, userID) -> string` — token efimero con scope reducido (solo el room donde esta el widget).
2. Frontend React:
   - Hook `useWidgets(roomID)` — lista widgets activos.
   - Componente `WidgetPanel`:
     - Tabs por widget activo + boton "+" para anadir.
     - Cada widget en iframe con `sandbox="allow-scripts allow-same-origin allow-forms allow-popups-to-escape-sandbox"`.
     - `iframe.referrerpolicy="no-referrer"`.
     - CSP: `frame-src https: data: blob:`.
   - `WidgetBridge` — clase JS que escucha `postMessage` del iframe e implementa Widget API v2:
     - `capabilities` handshake: el widget declara que necesita, el host pide consentimiento usuario (dialog Mantine).
     - `read_events`, `send_event`, `send_to_device`, `get_openid`, `m.always_on_screen`, etc.
     - Whitelist estricta de capabilities concedidas. Audit log de mensajes en `store.db`.
   - Layout: widgets se abren en panel lateral derecho (toggleable) o en modal fullscreen.
3. Widgets internos primer batch (proof of concept):
   - `widget-jitsi-fallback` — si LiveKit falla, fallback a Jitsi via widget (URL config).
   - `widget-agent-panel` — panel de control de agente: estado, ultima ejecucion, restart, view logs. Servido por `agents_and_robots` HTTP API (issue 0113 ya creando agent runner API).
   - `widget-kanban` — kanban inline embebido para tasks del room. Reusa `apps/kanban` (Go) servido en LAN.
   - `widget-issue-tracker` — widget que abre issue API (`0109m`).
4. Tests:
   - `e2e/test_widget_capabilities.sh` — widget pide capability, dialog aparece, deniega/acepta funciona.
   - `e2e/test_widget_send_event.sh` — widget con capability `send_event` envia msg al room.
   - `e2e/test_widget_sandbox.sh` — widget malicioso (intenta `top.location =`) es bloqueado por sandbox.

## Funciones del registry a crear

- `matrix_widget_state_go_infra` — CRUD state events `m.widget`.
- `widget_url_template_go_core` — substituye placeholders en URL.
- `widget_token_mint_go_infra` — token scoped a un widget+room+user.
- `WidgetBridge_ts_ui` — clase postMessage bridge Widget API v2 completa.
- `WidgetPanel_ts_ui` — UI tabs + iframes + permisos.
- `CapabilityConsentDialog_ts_ui` — dialog Mantine para consentimiento.

## Acceptance

- [ ] `/widget https://my.app` crea state event y abre iframe.
- [ ] Widget declara capability `m.send_event` -> dialog Mantine pide consentimiento.
- [ ] Widget concedido envia msg al room que aparece en timeline.
- [ ] Widget malicioso `<script>top.location='evil.com'</script>` bloqueado por sandbox.
- [ ] `agents_and_robots` publica widget panel y se ve embebido en el room del agente.
- [ ] Widget kanban inline funciona: drag&drop card persiste en DB del kanban.

## Notas

**Anti-criterios:**
- NO permitir `javascript:` ni `data:text/html` URLs (XSS).
- NO conceder capabilities sin consentimiento explicito del usuario (auditable).
- NO compartir el access_token Matrix del usuario al widget — usar siempre tokens scoped efimeros.

**Decisiones:**
- Widget API v2 (no v1) — soporta capabilities + tokens scoped.
- iframe sandbox sin `allow-top-navigation` (previene escape).
- CSP `frame-src https:` + permitir `data:`/`blob:` solo para widgets internos firmados.

**Roadmap post-DoD:**
- Widget marketplace interno: `widget-catalog` en `agents_and_robots` con widgets internos descubribles.
- Widget templates: un agente publica un widget HTML estatico subido al room (`mxc://`) y el cliente lo renderiza desde la URL `mxc -> http`.
- Cross-room widgets: widget que persiste entre rooms (TBD, requiere MSC propio).
