claude-plugin-wordpress-manager 2.12.2 → 2.14.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.
Files changed (62) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/CHANGELOG.md +94 -2
  3. package/agents/wp-accessibility-auditor.md +1 -1
  4. package/agents/wp-content-strategist.md +2 -2
  5. package/agents/wp-deployment-engineer.md +1 -1
  6. package/agents/wp-distribution-manager.md +1 -1
  7. package/agents/wp-monitoring-agent.md +1 -1
  8. package/agents/wp-performance-optimizer.md +1 -1
  9. package/agents/wp-security-auditor.md +1 -1
  10. package/agents/wp-site-manager.md +3 -3
  11. package/commands/wp-setup.md +2 -2
  12. package/docs/GUIDE.md +260 -21
  13. package/docs/VALIDATION.md +341 -0
  14. package/docs/guides/wp-ecommerce.md +4 -4
  15. package/docs/plans/2026-03-01-tier3-wcop-implementation.md +1 -1
  16. package/docs/plans/2026-03-01-tier4-5-implementation.md +1 -1
  17. package/docs/plans/2026-03-02-content-framework-architecture.md +612 -0
  18. package/docs/plans/2026-03-02-content-framework-strategic-reflections.md +228 -0
  19. package/docs/plans/2026-03-02-content-intelligence-phase2.md +560 -0
  20. package/docs/plans/2026-03-02-content-pipeline-phase1.md +456 -0
  21. package/docs/plans/2026-03-02-dashboard-kanban-design.md +761 -0
  22. package/docs/plans/2026-03-02-dashboard-kanban-implementation.md +598 -0
  23. package/docs/plans/2026-03-02-dashboard-strategy.md +363 -0
  24. package/docs/plans/2026-03-02-editorial-calendar-phase3.md +490 -0
  25. package/docs/validation/.gitkeep +0 -0
  26. package/docs/validation/dashboard.html +286 -0
  27. package/docs/validation/results.json +1705 -0
  28. package/package.json +16 -3
  29. package/scripts/context-scanner.mjs +446 -0
  30. package/scripts/dashboard-renderer.mjs +553 -0
  31. package/scripts/run-validation.mjs +1132 -0
  32. package/servers/wp-rest-bridge/build/server.js +17 -6
  33. package/servers/wp-rest-bridge/build/tools/index.js +0 -9
  34. package/servers/wp-rest-bridge/build/tools/plugin-repository.js +23 -31
  35. package/servers/wp-rest-bridge/build/tools/schema.js +10 -2
  36. package/servers/wp-rest-bridge/build/tools/unified-content.js +10 -2
  37. package/servers/wp-rest-bridge/build/wordpress.d.ts +0 -3
  38. package/servers/wp-rest-bridge/build/wordpress.js +16 -98
  39. package/servers/wp-rest-bridge/package.json +1 -0
  40. package/skills/wp-analytics/SKILL.md +153 -0
  41. package/skills/wp-analytics/references/signals-feed-schema.md +417 -0
  42. package/skills/wp-content/references/content-templates.md +1 -1
  43. package/skills/wp-content/references/seo-optimization.md +8 -8
  44. package/skills/wp-content-attribution/references/roi-calculation.md +1 -1
  45. package/skills/wp-content-attribution/references/utm-tracking-setup.md +5 -5
  46. package/skills/wp-content-generation/references/generation-workflow.md +2 -2
  47. package/skills/wp-content-pipeline/SKILL.md +461 -0
  48. package/skills/wp-content-pipeline/references/content-brief-schema.md +377 -0
  49. package/skills/wp-content-pipeline/references/site-config-schema.md +431 -0
  50. package/skills/wp-content-repurposing/references/auto-transform-pipeline.md +1 -1
  51. package/skills/wp-content-repurposing/references/email-newsletter.md +1 -1
  52. package/skills/wp-content-repurposing/references/platform-specs.md +2 -2
  53. package/skills/wp-content-repurposing/references/transform-templates.md +27 -27
  54. package/skills/wp-dashboard/SKILL.md +121 -0
  55. package/skills/wp-deploy/references/ssh-deploy.md +2 -2
  56. package/skills/wp-editorial-planner/SKILL.md +262 -0
  57. package/skills/wp-editorial-planner/references/editorial-schema.md +268 -0
  58. package/skills/wp-multilang-network/references/content-sync.md +3 -3
  59. package/skills/wp-multilang-network/references/network-architecture.md +1 -1
  60. package/skills/wp-multilang-network/references/seo-international.md +7 -7
  61. package/skills/wp-structured-data/references/schema-types.md +4 -4
  62. package/skills/wp-webhooks/references/payload-formats.md +3 -3
@@ -0,0 +1,761 @@
1
+ # Editorial Kanban Dashboard — Design Document
2
+
3
+ **Data**: 2026-03-02
4
+ **Versione**: 1.0.0
5
+ **Stato**: In design
6
+ **Parent**: [Dashboard Strategy](2026-03-02-dashboard-strategy.md)
7
+ **Deliverable**: Skill `wp-dashboard` + script `dashboard-renderer.mjs` + modulo condiviso `context-scanner.mjs`
8
+
9
+ ---
10
+
11
+ ## 1. Obiettivo
12
+
13
+ Generare un file HTML statico self-contained che visualizza lo stato editoriale di un sito WordPress come Kanban board. L'operatore invoca la skill `wp-dashboard`, lo script legge `.content-state/`, produce l'HTML e lo apre nel browser.
14
+
15
+ **Non-obiettivi**: interattività (drag & drop), aggiornamento live, persistenza stato proprio, dipendenze esterne.
16
+
17
+ ---
18
+
19
+ ## 2. Layout Visivo
20
+
21
+ ### 2.1 Struttura Generale
22
+
23
+ ```
24
+ ┌──────────────────────────────────────────────────────────────────────────────┐
25
+ │ HEADER: Editorial Dashboard — mysite.example.com — Marzo 2026 │
26
+ │ Generato: 2026-03-02 14:30 | Posts: 2/8 pubblicati | Pipeline: 1 ready │
27
+ ├──────────────────────────────────────────────────────────────────────────────┤
28
+ │ │
29
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
30
+ │ │ PLANNED │ │ DRAFT │ │ READY │ │SCHEDULED │ │PUBLISHED │ │
31
+ │ │ (3) │ │ (1) │ │ (1) │ │ (0) │ │ (2) │ │
32
+ │ ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ ├──────────┤ │
33
+ │ │ │ │ │ │ │ │ │ │ │ │
34
+ │ │ ┌──────┐ │ │ ┌──────┐ │ │ ┌──────┐ │ │ │ │ ┌──────┐ │ │
35
+ │ │ │ CARD │ │ │ │ CARD │ │ │ │ CARD │ │ │ (vuoto) │ │ │ CARD │ │ │
36
+ │ │ └──────┘ │ │ └──────┘ │ │ └──────┘ │ │ │ │ └──────┘ │ │
37
+ │ │ ┌──────┐ │ │ │ │ │ │ │ │ ┌──────┐ │ │
38
+ │ │ │ CARD │ │ │ │ │ │ │ │ │ │ CARD │ │ │
39
+ │ │ └──────┘ │ │ │ │ │ │ │ │ └──────┘ │ │
40
+ │ │ ┌──────┐ │ │ │ │ │ │ │ │ │ │
41
+ │ │ │ CARD │ │ │ │ │ │ │ │ │ │ │
42
+ │ │ └──────┘ │ │ │ │ │ │ │ │ │ │
43
+ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
44
+ │ │
45
+ ├──────────────────────────────────────────────────────────────────────────────┤
46
+ │ SIGNALS STRIP (se anomalie presenti) │
47
+ │ ▲ +120% "acqua premium" impressions | ▲ +85% LinkedIn referrals │
48
+ ├──────────────────────────────────────────────────────────────────────────────┤
49
+ │ FOOTER: WordPress Manager v2.14.0 | wp-dashboard skill │
50
+ └──────────────────────────────────────────────────────────────────────────────┘
51
+ ```
52
+
53
+ ### 2.2 Anatomia della Card
54
+
55
+ Ogni card rappresenta un contenuto nel ciclo editoriale.
56
+
57
+ ```
58
+ ┌─────────────────────────────┐
59
+ │ Mar 18 📝 │ ← data + icona tipo (📝 post, 📄 page)
60
+ │ │
61
+ │ Acqua premium: perché le │ ← titolo (troncato a 60 char)
62
+ │ ricerche sono esplose... │
63
+ │ │
64
+ │ BRF-2026-005 │ ← brief ID (se esiste)
65
+ │ #wellness #premium-water │ ← categorie/tag principali
66
+ │ │
67
+ │ 🔗 in 📧 nl │ ← icone canali distribuzione
68
+ └─────────────────────────────┘
69
+ ```
70
+
71
+ **Varianti card per stato**:
72
+
73
+ | Stato | Colore bordo sinistro | Note |
74
+ |-------|----------------------|------|
75
+ | `planned` | grigio `#94a3b8` | Titolo può essere `[da assegnare]` |
76
+ | `draft` | giallo `#eab308` | Ha Brief ID |
77
+ | `ready` | blu `#3b82f6` | Pronto per scheduling |
78
+ | `scheduled` | viola `#8b5cf6` | Ha Post ID, mostra data scheduling |
79
+ | `published` | verde `#22c55e` | Ha Post ID + URL |
80
+
81
+ **Card "da assegnare"** (planned, senza titolo):
82
+
83
+ ```
84
+ ┌─────────────────────────────┐
85
+ │ Mar 20 📝 │
86
+ │ │
87
+ │ [da assegnare] │ ← testo grigio, italic
88
+ │ │
89
+ │ — │ ← nessun brief ID
90
+ └─────────────────────────────┘
91
+ ```
92
+
93
+ ### 2.3 Header — Metriche Aggregate
94
+
95
+ Il header contiene una riga di metriche riassuntive:
96
+
97
+ ```
98
+ Editorial Dashboard — mysite.example.com — Marzo 2026
99
+ Generato: 2026-03-02 14:30 | Posts: 2/8 pubblicati | Pipeline: 1 ready, 1 draft | Next: Mar 11
100
+ ```
101
+
102
+ | Metrica | Calcolo |
103
+ |---------|---------|
104
+ | Posts X/Y pubblicati | `count(status=published)` / `goals.posts_target` |
105
+ | Pipeline: N ready, N draft | count per status nei brief attivi |
106
+ | Next: data | prima data con `status` non `published` e non `planned-vuoto` |
107
+
108
+ ### 2.4 Signals Strip
109
+
110
+ Striscia opzionale sotto il Kanban, presente solo se `signals-feed.md` contiene anomalie:
111
+
112
+ ```
113
+ ┌──────────────────────────────────────────────────────────────────────────────┐
114
+ │ ⚡ Signals ▲ +120% "acqua premium" impressions → content cluster │
115
+ │ ▲ +85% LinkedIn referrals → scale posting frequency │
116
+ │ ▲ +47% /premium-water-benefici pageviews → investigate │
117
+ └──────────────────────────────────────────────────────────────────────────────┘
118
+ ```
119
+
120
+ Ogni anomalia mostra: direzione (`▲`/`▼`), delta percentuale, entità, azione suggerita.
121
+
122
+ ### 2.5 Palette Colori
123
+
124
+ Coerenza con il brand system AcmeBrand/MySite ma neutrale (il dashboard è uno strumento operativo, non un artefatto brand):
125
+
126
+ ```css
127
+ /* Background & Structure */
128
+ --bg-page: #f8fafc; /* slate-50 */
129
+ --bg-column: #f1f5f9; /* slate-100 */
130
+ --bg-card: #ffffff;
131
+ --border: #e2e8f0; /* slate-200 */
132
+ --text-primary: #1e293b; /* slate-800 */
133
+ --text-secondary:#64748b; /* slate-500 */
134
+ --text-muted: #94a3b8; /* slate-400 */
135
+
136
+ /* Status Colors */
137
+ --status-planned: #94a3b8; /* slate-400 */
138
+ --status-draft: #eab308; /* yellow-500 */
139
+ --status-ready: #3b82f6; /* blue-500 */
140
+ --status-scheduled: #8b5cf6; /* violet-500 */
141
+ --status-published: #22c55e; /* green-500 */
142
+
143
+ /* Signals */
144
+ --signal-up: #22c55e; /* green */
145
+ --signal-down: #ef4444; /* red */
146
+
147
+ /* Typography */
148
+ font-family: system-ui, -apple-system, sans-serif;
149
+ ```
150
+
151
+ ---
152
+
153
+ ## 3. Data Flow: SCAN → AGGREGATE → RENDER
154
+
155
+ ### 3.1 SCAN — Lettura File
156
+
157
+ Lo scanner legge i file `.content-state/` e produce un oggetto JavaScript strutturato.
158
+
159
+ **Input files**:
160
+
161
+ | File | Parsing | Output |
162
+ |------|---------|--------|
163
+ | `{site_id}.config.md` | Frontmatter YAML | `{ site_id, site_url, brand, defaults, channels, seo, cadence }` |
164
+ | `YYYY-MM-editorial.state.md` | Frontmatter YAML + tabelle Markdown | `{ calendar_id, period, goals, entries[] }` |
165
+ | `pipeline-active/*.brief.md` | Frontmatter YAML per ogni file | `{ briefs_active[] }` |
166
+ | `pipeline-archive/*.brief.md` | Frontmatter YAML per ogni file | `{ briefs_archived[] }` |
167
+ | `signals-feed.md` | Frontmatter YAML + tabella anomalie | `{ signals[] }` |
168
+
169
+ **Parsing delle tabelle Markdown**:
170
+
171
+ Il calendario editoriale usa tabelle con colonne fisse:
172
+
173
+ ```markdown
174
+ | Data | Titolo | Tipo | Status | Brief ID | Post ID | Canali |
175
+ ```
176
+
177
+ Lo scanner converte ogni riga in un oggetto:
178
+
179
+ ```javascript
180
+ {
181
+ date: "2026-03-18",
182
+ title: "Acqua premium: perché le ricerche sono esplose del 120%",
183
+ type: "post",
184
+ status: "ready",
185
+ briefId: "BRF-2026-005",
186
+ postId: null,
187
+ channels: ["linkedin", "newsletter"]
188
+ }
189
+ ```
190
+
191
+ **Regole di parsing**:
192
+ - Le righe con titolo `[da assegnare]` hanno `title: null`
193
+ - Post ID `—` è convertito a `null`
194
+ - Canali sono splittati per `, ` (virgola + spazio)
195
+ - Le date sono nel formato `Mon DD` (es. `Mar 18`) e vengono risolte con anno/mese dal `period` del calendar
196
+
197
+ **Risultato SCAN**:
198
+
199
+ ```javascript
200
+ {
201
+ site: {
202
+ id: "mysite",
203
+ url: "https://mysite.example.com",
204
+ brand: { tone: "...", language: "it", ... },
205
+ cadence: { posts_per_week: 3, preferred_days: [...], publish_time: "09:00" },
206
+ channels: { linkedin: { enabled: true, ... }, ... }
207
+ },
208
+ calendar: {
209
+ id: "CAL-2026-03",
210
+ period: "2026-03-01..2026-03-31",
211
+ goals: { posts_target: 8, posts_published: 2, ... },
212
+ entries: [
213
+ { date: "2026-03-04", title: "Acqua premium: 5 benefici...", type: "post", status: "published", briefId: "BRF-2026-001", postId: 1234, channels: ["linkedin", "newsletter"] },
214
+ { date: "2026-03-06", title: "Come il frutto mediterraneo diventa bevanda", type: "post", status: "published", briefId: "BRF-2026-002", postId: 1235, channels: ["linkedin", "twitter"] },
215
+ { date: "2026-03-11", title: "Zero calorie, tutto gusto: la scienza", type: "post", status: "ready", briefId: "BRF-2026-003", postId: null, channels: ["linkedin", "newsletter"] },
216
+ { date: "2026-03-13", title: "Mediterraneo e sostenibilità: la filiera", type: "post", status: "draft", briefId: "BRF-2026-004", postId: null, channels: ["linkedin"] },
217
+ { date: "2026-03-18", title: "Acqua premium: perché le ricerche...", type: "post", status: "ready", briefId: "BRF-2026-005", postId: null, channels: ["linkedin", "newsletter"] },
218
+ { date: "2026-03-20", title: null, type: "post", status: "planned", briefId: null, postId: null, channels: [] },
219
+ { date: "2026-03-25", title: null, type: "post", status: "planned", briefId: null, postId: null, channels: [] },
220
+ { date: "2026-03-27", title: null, type: "post", status: "planned", briefId: null, postId: null, channels: [] }
221
+ ]
222
+ },
223
+ briefs: {
224
+ active: [
225
+ { briefId: "BRF-2026-005", status: "ready", title: "Acqua premium: perché...", siteId: "mysite", channels: ["linkedin", "newsletter"], signalRef: "FEED-mysite-2026-02 → Keyword:acqua premium +120%" }
226
+ ],
227
+ archived: [
228
+ { briefId: "BRF-2026-001", status: "published", title: "I Benefici dell'Acqua Premium...", postId: 2456, postUrl: "https://mysite.example.com/benefici-..." }
229
+ ]
230
+ },
231
+ signals: {
232
+ feedId: "FEED-mysite-2026-02",
233
+ period: "2026-02-01..2026-02-28",
234
+ anomalies: [
235
+ { entity: "Keyword:acqua premium", metric: "search_impressions", delta: "+120%", pattern: "Search Intent Shift", action: "Investigate: content cluster opportunity" },
236
+ { entity: "Source:linkedin", metric: "referral_sessions", delta: "+85%", pattern: "Early-Adopter Surge", action: "Scale: increase posting frequency on linkedin" },
237
+ { entity: "Page:/premium-water-benefici", metric: "pageviews", delta: "+47%", pattern: "Unclassified anomaly", action: "Review: investigate cause of +47% change in pageviews" }
238
+ ]
239
+ }
240
+ }
241
+ ```
242
+
243
+ ### 3.2 AGGREGATE — Metriche Derivate
244
+
245
+ Dall'output SCAN, calcola metriche aggregate per il rendering:
246
+
247
+ ```javascript
248
+ {
249
+ // Progress metrics
250
+ postsPublished: 2,
251
+ postsTarget: 8,
252
+ progressPercent: 25, // 2/8 * 100
253
+
254
+ // Pipeline counts (per column)
255
+ columns: {
256
+ planned: 3, // entries con status=planned
257
+ draft: 1, // entries con status=draft
258
+ ready: 2, // entries con status=ready (incluso da brief attivi)
259
+ scheduled: 0, // entries con status=scheduled
260
+ published: 2 // entries con status=published
261
+ },
262
+
263
+ // Timeline
264
+ nextDeadline: {
265
+ date: "2026-03-11",
266
+ title: "Zero calorie, tutto gusto: la scienza",
267
+ status: "ready",
268
+ daysFromNow: 9
269
+ },
270
+
271
+ // Channel distribution
272
+ channelUsage: {
273
+ linkedin: 5, // entries che includono linkedin
274
+ newsletter: 3,
275
+ twitter: 1
276
+ },
277
+
278
+ // Signals summary
279
+ signalsCount: 3,
280
+ signalsHighest: { entity: "acqua premium", delta: "+120%", pattern: "Search Intent Shift" },
281
+
282
+ // Calendar fill rate
283
+ fillRate: 62.5, // 5/8 entries con titolo assegnato * 100
284
+
285
+ // Generation metadata
286
+ generatedAt: "2026-03-02T14:30:00+01:00",
287
+ generatorVersion: "1.0.0"
288
+ }
289
+ ```
290
+
291
+ ### 3.3 RENDER — Generazione HTML
292
+
293
+ Il renderer prende `entries[]` (raggruppate per status) e `metrics`, produce un HTML self-contained.
294
+
295
+ **Template strategy**: template literal ES6. Nessun template engine esterno.
296
+
297
+ **Struttura HTML**:
298
+
299
+ ```html
300
+ <!DOCTYPE html>
301
+ <html lang="it">
302
+ <head>
303
+ <meta charset="UTF-8">
304
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
305
+ <title>Editorial Dashboard — {site_id} — {month} {year}</title>
306
+ <style>
307
+ /* Tutti gli stili inline — vedi sezione 2.5 per la palette */
308
+ /* ~150 righe CSS: reset, layout grid, card, header, signals strip */
309
+ </style>
310
+ </head>
311
+ <body>
312
+ <header>
313
+ <!-- Metriche aggregate: titolo, data generazione, progress bar, pipeline counts -->
314
+ </header>
315
+ <main class="kanban">
316
+ <!-- 5 colonne: planned | draft | ready | scheduled | published -->
317
+ <!-- Ogni colonna contiene N card dalla entries[] filtrata per status -->
318
+ </main>
319
+ <section class="signals-strip">
320
+ <!-- Anomalie da signals-feed, se presenti -->
321
+ </section>
322
+ <footer>
323
+ <!-- Versione plugin, skill, timestamp -->
324
+ </footer>
325
+ </body>
326
+ </html>
327
+ ```
328
+
329
+ **Dimensioni target**: < 30 KB per un mese con 8-12 entries e 3-5 anomalie.
330
+
331
+ ---
332
+
333
+ ## 4. Architettura Script
334
+
335
+ ### 4.1 Moduli
336
+
337
+ ```
338
+ scripts/
339
+ ├── context-scanner.mjs ← SCAN + AGGREGATE (modulo condiviso)
340
+ │ export: scanContentState(contentStatePath, siteId)
341
+ │ export: aggregateMetrics(rawData, viewType)
342
+ │ export: renderContextSnippet(metrics, sliceType) ← per Fase B (step 0)
343
+
344
+ └── dashboard-renderer.mjs ← RENDER HTML + open browser
345
+ import: { scanContentState, aggregateMetrics } from './context-scanner.mjs'
346
+ export: renderKanbanHTML(rawData, metrics)
347
+ main: scan → aggregate → render → write file → open browser
348
+ ```
349
+
350
+ ### 4.2 `context-scanner.mjs` — API
351
+
352
+ ```javascript
353
+ /**
354
+ * Scans .content-state/ directory for a specific site.
355
+ *
356
+ * @param {string} contentStatePath - Path to .content-state/ directory
357
+ * @param {string} siteId - Site identifier (e.g., "mysite")
358
+ * @returns {object} Raw data: { site, calendar, briefs, signals }
359
+ */
360
+ export function scanContentState(contentStatePath, siteId) { ... }
361
+
362
+ /**
363
+ * Computes aggregate metrics from raw scan data.
364
+ *
365
+ * @param {object} rawData - Output of scanContentState()
366
+ * @param {string} viewType - "kanban" | "signals" | "distribution" | "health"
367
+ * @returns {object} Aggregated metrics for the requested view
368
+ */
369
+ export function aggregateMetrics(rawData, viewType) { ... }
370
+
371
+ /**
372
+ * Renders a compact terminal snippet for step 0 context.
373
+ * (Fase B — implementazione differita)
374
+ *
375
+ * @param {object} metrics - Output of aggregateMetrics()
376
+ * @param {string} sliceType - "pipeline" | "calendar" | "signals"
377
+ * @returns {string} Formatted terminal text (3-5 lines)
378
+ */
379
+ export function renderContextSnippet(metrics, sliceType) { ... }
380
+ ```
381
+
382
+ ### 4.3 `dashboard-renderer.mjs` — CLI
383
+
384
+ ```bash
385
+ # Uso:
386
+ node scripts/dashboard-renderer.mjs # sito da config, mese corrente
387
+ node scripts/dashboard-renderer.mjs --site=mysite # sito specifico
388
+ node scripts/dashboard-renderer.mjs --month=2026-04 # mese specifico
389
+ node scripts/dashboard-renderer.mjs --output=/tmp/dash.html # output specifico
390
+ node scripts/dashboard-renderer.mjs --no-open # non aprire browser
391
+ ```
392
+
393
+ **Flusso main()**:
394
+
395
+ ```
396
+ 1. Parse CLI args (--site, --month, --output, --no-open)
397
+ 2. Resolve contentStatePath da plugin root
398
+ 3. Se --site non fornito:
399
+ a. Cerca tutti i *.config.md in .content-state/
400
+ b. Se uno solo → usa quello
401
+ c. Se multipli → errore con lista siti disponibili
402
+ 4. Se --month non fornito → usa mese corrente
403
+ 5. scanContentState(contentStatePath, siteId)
404
+ 6. aggregateMetrics(rawData, "kanban")
405
+ 7. renderKanbanHTML(rawData, metrics) → htmlString
406
+ 8. Scrivi htmlString in file temporaneo (o --output)
407
+ Default: .content-state/.dashboard-{siteId}-{month}.html
408
+ 9. Se non --no-open: exec("xdg-open {filepath}") su Linux
409
+ ```
410
+
411
+ ### 4.4 YAML Frontmatter Parsing
412
+
413
+ Lo scanner parsa il frontmatter YAML manualmente (no dipendenze):
414
+
415
+ ```javascript
416
+ function parseFrontmatter(content) {
417
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
418
+ if (!match) return { frontmatter: {}, body: content };
419
+ // Simple YAML parser for flat/nested structures
420
+ // Handles: strings, numbers, arrays (inline [a, b] and multi-line - a\n- b), nested objects
421
+ // Does NOT need: anchors, aliases, multi-document, complex types
422
+ }
423
+ ```
424
+
425
+ **Alternativa**: usare il pacchetto `yaml` già presente come dipendenza transitiva del SDK MCP in `servers/wp-rest-bridge/node_modules/`. Importarlo via `createRequire`.
426
+
427
+ **Decisione**: usare `yaml` da node_modules se disponibile (più robusto), fallback a parser manuale semplice. Verificare disponibilità a implementazione.
428
+
429
+ ### 4.5 Markdown Table Parsing
430
+
431
+ Le tabelle editoriali hanno formato fisso:
432
+
433
+ ```markdown
434
+ | Data | Titolo | Tipo | Status | Brief ID | Post ID | Canali |
435
+ |------|--------|------|--------|----------|---------|--------|
436
+ | Mar 4 | Acqua premium: 5 benefici scientifici | post | published | BRF-2026-001 | 1234 | linkedin, newsletter |
437
+ ```
438
+
439
+ Parser dedicato:
440
+
441
+ ```javascript
442
+ function parseEditorialTable(markdownBody) {
443
+ // 1. Split body in sezioni per "## Settimana N"
444
+ // 2. Per ogni sezione, trova la tabella (righe che iniziano con |)
445
+ // 3. Skip header row (| Data | ...) e separator row (|------|...)
446
+ // 4. Per ogni data row: split per |, trim, map a oggetto
447
+ // 5. Gestisci valori speciali: "—" → null, "[da assegnare]" → null per title
448
+ // Return: array di entry objects
449
+ }
450
+ ```
451
+
452
+ ### 4.6 Dipendenze
453
+
454
+ | Dipendenza | Tipo | Note |
455
+ |------------|------|------|
456
+ | `node:fs/promises` | Built-in | Lettura file |
457
+ | `node:path` | Built-in | Path resolution |
458
+ | `node:child_process` | Built-in | `exec` per `xdg-open` |
459
+ | `node:url` | Built-in | `fileURLToPath` per ESM |
460
+ | `yaml` (opzionale) | From node_modules | Parsing YAML robusto, via `createRequire` |
461
+
462
+ **Zero nuove dipendenze npm da installare.**
463
+
464
+ ---
465
+
466
+ ## 5. Skill Definition
467
+
468
+ ### 5.1 `skills/wp-dashboard/SKILL.md`
469
+
470
+ ```yaml
471
+ ---
472
+ name: wp-dashboard
473
+ description: This skill should be used when the user asks to "show dashboard",
474
+ "show editorial status", "open kanban", "visualize content state",
475
+ "show content overview", "mostra dashboard", "apri kanban", or wants
476
+ a visual overview of the editorial pipeline. Generates a self-contained
477
+ HTML Kanban board from .content-state/ files and opens it in the browser.
478
+ version: 1.0.0
479
+ ---
480
+ ```
481
+
482
+ **Workflow della skill**:
483
+
484
+ 1. Determinare il sito target (da contesto conversazione o chiedere all'utente)
485
+ 2. Determinare il mese (default: corrente)
486
+ 3. Eseguire lo script:
487
+ ```bash
488
+ node scripts/dashboard-renderer.mjs --site={siteId} --month={YYYY-MM}
489
+ ```
490
+ 4. Confermare all'utente che il dashboard è stato aperto nel browser
491
+ 5. Se l'utente chiede modifiche al contenuto basandosi sul dashboard → suggerire la skill appropriata (`wp-content-pipeline`, `wp-editorial-planner`)
492
+
493
+ ### 5.2 Trigger e Routing
494
+
495
+ La skill è invocata esplicitamente dall'utente. Non è un step automatico di altre skill (quello è Fase B — context snippet).
496
+
497
+ **Frasi trigger**:
498
+ - "mostra il dashboard" / "show dashboard"
499
+ - "apri il kanban" / "open kanban"
500
+ - "stato editoriale" / "editorial status"
501
+ - "panoramica contenuti" / "content overview"
502
+ - "dove siamo con i post?" / "where are we with posts?"
503
+
504
+ ---
505
+
506
+ ## 6. HTML Template — Specifiche di Dettaglio
507
+
508
+ ### 6.1 CSS Layout
509
+
510
+ **Kanban grid**: CSS Grid con 5 colonne di larghezza uguale.
511
+
512
+ ```css
513
+ .kanban {
514
+ display: grid;
515
+ grid-template-columns: repeat(5, 1fr);
516
+ gap: 16px;
517
+ padding: 24px;
518
+ min-height: 400px;
519
+ }
520
+
521
+ .column {
522
+ background: var(--bg-column);
523
+ border-radius: 8px;
524
+ padding: 12px;
525
+ display: flex;
526
+ flex-direction: column;
527
+ gap: 8px;
528
+ }
529
+ ```
530
+
531
+ **Card**: Box bianco con bordo sinistro colorato.
532
+
533
+ ```css
534
+ .card {
535
+ background: var(--bg-card);
536
+ border-radius: 6px;
537
+ border-left: 4px solid var(--status-color);
538
+ padding: 12px;
539
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
540
+ }
541
+ ```
542
+
543
+ **Responsive**: A viewport < 768px le colonne diventano verticali (stack).
544
+
545
+ ```css
546
+ @media (max-width: 768px) {
547
+ .kanban {
548
+ grid-template-columns: 1fr;
549
+ }
550
+ }
551
+ ```
552
+
553
+ ### 6.2 Header con Progress Bar
554
+
555
+ ```html
556
+ <header>
557
+ <h1>Editorial Dashboard — mysite.example.com — Marzo 2026</h1>
558
+ <div class="meta">
559
+ Generato: 2026-03-02 14:30 | Next: Mar 11 — "Zero calorie, tutto gusto"
560
+ </div>
561
+ <div class="progress-bar">
562
+ <div class="progress-fill" style="width: 25%"></div>
563
+ <span>2/8 pubblicati</span>
564
+ </div>
565
+ <div class="pipeline-counts">
566
+ <span class="badge planned">3 planned</span>
567
+ <span class="badge draft">1 draft</span>
568
+ <span class="badge ready">2 ready</span>
569
+ <span class="badge scheduled">0 scheduled</span>
570
+ <span class="badge published">2 published</span>
571
+ </div>
572
+ </header>
573
+ ```
574
+
575
+ ### 6.3 Card HTML
576
+
577
+ ```html
578
+ <div class="card" style="--status-color: var(--status-ready)">
579
+ <div class="card-header">
580
+ <span class="card-date">Mar 18</span>
581
+ <span class="card-type" title="post">&#x1F4DD;</span>
582
+ </div>
583
+ <div class="card-title">Acqua premium: perch&eacute; le ricerche sono esplose del 120%</div>
584
+ <div class="card-meta">
585
+ <span class="brief-id">BRF-2026-005</span>
586
+ </div>
587
+ <div class="card-tags">
588
+ <span class="tag">#wellness</span>
589
+ <span class="tag">#premium-water</span>
590
+ </div>
591
+ <div class="card-channels">
592
+ <span class="channel" title="LinkedIn">in</span>
593
+ <span class="channel" title="Newsletter">nl</span>
594
+ </div>
595
+ </div>
596
+ ```
597
+
598
+ **Card "da assegnare"**:
599
+
600
+ ```html
601
+ <div class="card card--empty" style="--status-color: var(--status-planned)">
602
+ <div class="card-header">
603
+ <span class="card-date">Mar 20</span>
604
+ <span class="card-type" title="post">&#x1F4DD;</span>
605
+ </div>
606
+ <div class="card-title card-title--placeholder">[da assegnare]</div>
607
+ </div>
608
+ ```
609
+
610
+ ### 6.4 Signals Strip HTML
611
+
612
+ ```html
613
+ <section class="signals-strip">
614
+ <h2>Signals</h2>
615
+ <div class="signal-list">
616
+ <div class="signal signal--up">
617
+ <span class="signal-arrow">&#x25B2;</span>
618
+ <span class="signal-delta">+120%</span>
619
+ <span class="signal-entity">&quot;acqua premium&quot; impressions</span>
620
+ <span class="signal-action">content cluster opportunity</span>
621
+ </div>
622
+ <!-- ... altre anomalie ... -->
623
+ </div>
624
+ </section>
625
+ ```
626
+
627
+ ### 6.5 Icone Canali
628
+
629
+ Abbreviazioni testuali (no icon font, no SVG inline per semplicità):
630
+
631
+ | Canale | Abbrev | Title attribute |
632
+ |--------|--------|-----------------|
633
+ | linkedin | `in` | LinkedIn |
634
+ | twitter | `tw` | Twitter/X |
635
+ | newsletter / mailchimp | `nl` | Newsletter |
636
+ | buffer | `bf` | Buffer |
637
+
638
+ Stile: pill badge con background colorato.
639
+
640
+ ```css
641
+ .channel {
642
+ display: inline-block;
643
+ padding: 2px 6px;
644
+ border-radius: 4px;
645
+ font-size: 11px;
646
+ font-weight: 600;
647
+ text-transform: uppercase;
648
+ }
649
+ .channel[title="LinkedIn"] { background: #0077b5; color: white; }
650
+ .channel[title="Twitter/X"] { background: #1da1f2; color: white; }
651
+ .channel[title="Newsletter"]{ background: #f59e0b; color: white; }
652
+ .channel[title="Buffer"] { background: #168eea; color: white; }
653
+ ```
654
+
655
+ ---
656
+
657
+ ## 7. Gestione Edge Case
658
+
659
+ ### 7.1 File Mancanti
660
+
661
+ | Scenario | Comportamento |
662
+ |----------|--------------|
663
+ | `{site_id}.config.md` non esiste | Errore: "Site config not found for '{siteId}'. Run `wp-editorial-planner` first." |
664
+ | `YYYY-MM-editorial.state.md` non esiste | Warning: "No calendar for {month}. Dashboard shows empty Kanban." Genera HTML con tutte le colonne vuote. |
665
+ | `signals-feed.md` non esiste | Signals strip non renderizzata. |
666
+ | `pipeline-active/` vuoto | Sezione brief nella card non mostra dati aggiuntivi. |
667
+ | `pipeline-archive/` vuoto | Nessun impatto (i brief archiviati non appaiono nel Kanban). |
668
+
669
+ ### 7.2 Dati Inconsistenti
670
+
671
+ | Scenario | Comportamento |
672
+ |----------|--------------|
673
+ | Calendar entry ha Brief ID ma il file brief non esiste | Card mostra il Brief ID con badge `⚠ missing` |
674
+ | Brief ha status diverso da calendar entry | Usa lo status del calendar (il calendar è la source of truth per il Kanban) |
675
+ | Entry senza data | Skip entry, non appare nel Kanban |
676
+ | Titolo > 60 caratteri | Tronca con `...` nel card title, titolo completo nel `title` attribute (hover) |
677
+
678
+ ### 7.3 Multi-Sito (Futuro)
679
+
680
+ Quando ci sono più siti configurati:
681
+
682
+ - `--site` è obbligatorio
683
+ - Se omesso, lo script lista i siti disponibili e esce
684
+ - Ogni sito ha il suo HTML separato
685
+ - Non esiste (per ora) un dashboard multi-sito aggregato
686
+
687
+ ---
688
+
689
+ ## 8. File Output
690
+
691
+ ### 8.1 Naming Convention
692
+
693
+ ```
694
+ .content-state/.dashboard-{siteId}-{YYYY-MM}.html
695
+ ```
696
+
697
+ Esempio: `.content-state/.dashboard-mysite-2026-03.html`
698
+
699
+ - Prefisso `.` (hidden file) perché è un artefatto generato, non dati
700
+ - Nella directory `.content-state/` per coerenza (è uno "stato" derivato)
701
+ - **Gitignored**: aggiungere `.content-state/.dashboard-*.html` al `.gitignore`
702
+
703
+ ### 8.2 Apertura Browser
704
+
705
+ ```javascript
706
+ import { exec } from 'node:child_process';
707
+ import { platform } from 'node:os';
708
+
709
+ function openInBrowser(filepath) {
710
+ const cmd = platform() === 'darwin' ? 'open' :
711
+ platform() === 'win32' ? 'start' :
712
+ 'xdg-open';
713
+ exec(`${cmd} "${filepath}"`);
714
+ }
715
+ ```
716
+
717
+ ---
718
+
719
+ ## 9. Verifica Design
720
+
721
+ ### 9.1 Checklist Pre-Implementazione
722
+
723
+ - [ ] La palette colori è leggibile su sfondo chiaro?
724
+ - [ ] Le abbreviazioni canali sono intuitive?
725
+ - [ ] L'HTML è < 30 KB con i dati di esempio (8 entries, 3 anomalie)?
726
+ - [ ] Il layout responsivo funziona a 768px?
727
+ - [ ] Il parser frontmatter gestisce tutti i campi dei file reali?
728
+ - [ ] Il parser tabelle gestisce `—`, `[da assegnare]`, virgole nei canali?
729
+ - [ ] `xdg-open` funziona su WSL2?
730
+
731
+ ### 9.2 Test con Dati Reali
732
+
733
+ Il dataset di test è lo stato corrente di `.content-state/` per mysite (Marzo 2026):
734
+ - 8 entries nel calendario (2 published, 1 draft, 2 ready, 3 planned)
735
+ - 1 brief attivo (BRF-2026-005)
736
+ - 1 brief archiviato (BRF-2026-001)
737
+ - 3 anomalie nel signals feed
738
+
739
+ Questo dataset è sufficiente per validare tutti i casi: card con titolo, card vuota, card con brief, card senza brief, card pubblicata con post ID.
740
+
741
+ ---
742
+
743
+ ## 10. Documenti Derivati
744
+
745
+ Da questo design document deriva:
746
+
747
+ ```
748
+ docs/plans/2026-03-02-dashboard-kanban-design.md ← QUESTO DOCUMENTO
749
+ └── docs/plans/2026-03-02-dashboard-kanban-implementation.md (prossimo passo)
750
+ ```
751
+
752
+ Il piano di implementazione conterrà i task operativi TDD per:
753
+ 1. `scripts/context-scanner.mjs` (SCAN + AGGREGATE)
754
+ 2. `scripts/dashboard-renderer.mjs` (RENDER + CLI)
755
+ 3. `skills/wp-dashboard/SKILL.md`
756
+ 4. Test con dati reali
757
+ 5. Version bump e changelog
758
+
759
+ ---
760
+
761
+ *Design document per il Kanban editoriale del WordPress Manager Plugin. Fase A della Dashboard Strategy.*