@cyber-dash-tech/revela 0.7.8 → 0.7.9

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/README.md CHANGED
@@ -351,7 +351,7 @@ You can ask Revela to create a new local design interactively:
351
351
  /revela designs-new my-design
352
352
  ```
353
353
 
354
- The agent will interview you for visual references, summarize a design brief for confirmation, then save `DESIGN.md` and `preview.html` into your local Revela designs directory. The default structural base is an internal neutral `starter` design, which is hidden from the normal design list. Use `--base summit` or `--base monet` only when you want to derive from those specific styles.
354
+ The agent will interview you for visual references, summarize a design brief for confirmation, then save `DESIGN.md` and `preview.html` into your local Revela designs directory. For AI-authored designs, `preview.html` is required: it must include cover and closing slides, and it must showcase every `@component:*` before `revela-designs-author` will accept the package. The default structural base is an internal neutral `starter` design, which is hidden from the normal design list. Use `--base summit` or `--base monet` only when you want to derive from those specific styles.
355
355
 
356
356
  Refine an existing local design:
357
357
 
@@ -374,7 +374,7 @@ Recommended structure:
374
374
  ```text
375
375
  my-design/
376
376
  ├── DESIGN.md
377
- └── preview.html optional, but recommended for humans
377
+ └── preview.html required for AI-authored designs
378
378
  ```
379
379
 
380
380
  `DESIGN.md` starts with frontmatter metadata:
@@ -565,7 +565,9 @@ If a design has no markers, Revela falls back to injecting the full `DESIGN.md`
565
565
  - Put the non-negotiable rules in `foundation` and `rules`; do not hide essential constraints only inside one layout
566
566
  - Keep layout names semantically meaningful; they become the vocabulary the model sees in the layout index
567
567
  - If your design defines a custom CSS class, document that class inside `DESIGN.md`; QA checks can flag classes not present in the design vocabulary
568
- - Add `preview.html` when possible so humans can inspect the design before activating it
568
+ - For AI-authored designs, `preview.html` must include `<section class="slide" data-slide-role="cover">` and `<section class="slide" data-slide-role="closing">`
569
+ - For AI-authored designs, `preview.html` must visibly showcase every `@component:*` and mark each sample with `data-preview-component="<component-name>"`; otherwise `revela-designs-author create/validate` will fail
570
+ - When the design supports chart styling, include a 3x3 ECharts gallery with at least 9 chart examples in `preview.html`; this is a quality requirement for the agent workflow, not a hard validation blocker
569
571
 
570
572
  Install a custom design:
571
573
 
package/README.zh-CN.md CHANGED
@@ -317,7 +317,7 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
317
317
  /revela designs-new my-design
318
318
  ```
319
319
 
320
- Agent 会先询问你的审美参考,整理设计 brief 并等待确认,然后把 `DESIGN.md` 和 `preview.html` 保存到本地 Revela designs 目录。默认结构底座是内部中性 `starter` design,它不会出现在普通 design 列表中。只有当你明确想从 `summit` 或 `monet` 的具体风格派生时,才建议使用 `--base summit` 或 `--base monet`。
320
+ Agent 会先询问你的审美参考,整理设计 brief 并等待确认,然后把 `DESIGN.md` 和 `preview.html` 保存到本地 Revela designs 目录。对 AI 生成的 design,`preview.html` 是必需验收面:它必须包含 cover 和 closing 页,并且必须展示所有 `@component:*`,否则 `revela-designs-author` 不会接受这个包。默认结构底座是内部中性 `starter` design,它不会出现在普通 design 列表中。只有当你明确想从 `summit` 或 `monet` 的具体风格派生时,才建议使用 `--base summit` 或 `--base monet`。
321
321
 
322
322
  调整已有本地 design:
323
323
 
@@ -340,7 +340,7 @@ Agent 会询问你想修改什么,读取当前 design,整理 edit brief 并
340
340
  ```text
341
341
  my-design/
342
342
  ├── DESIGN.md
343
- └── preview.html 可选,但推荐,方便人工预览
343
+ └── preview.html AI 生成 design 必需
344
344
  ```
345
345
 
346
346
  `DESIGN.md` 顶部使用 frontmatter:
@@ -530,7 +530,9 @@ Prompt 注入规则:
530
530
  - 把不可妥协的规则放进 `foundation` 和 `rules`,不要只藏在某个 layout 里
531
531
  - layout 名称尽量语义化,因为模型在 layout index 里首先看到的就是这些名字
532
532
  - 如果定义了自定义 CSS class,记得在 `DESIGN.md` 里写出来;QA 会检查 design 词汇表之外的新 class
533
- - 如果条件允许,最好同时提供 `preview.html`,方便人工预览和验收
533
+ - AI 生成的 design 必须在 `preview.html` 中包含 `<section class="slide" data-slide-role="cover">` 和 `<section class="slide" data-slide-role="closing">`
534
+ - AI 生成的 design 必须在 `preview.html` 中可视化展示每个 `@component:*`,并用 `data-preview-component="<component-name>"` 标记;否则 `revela-designs-author create/validate` 会失败
535
+ - 如果 design 支持图表样式,`preview.html` 应包含 3x3 ECharts 九宫格,至少展示 9 个 chart 示例;这是 agent 工作流的质量要求,不是硬校验 blocker
534
536
 
535
537
  安装自定义 design:
536
538
 
@@ -692,35 +692,139 @@ Small page number utility.
692
692
  <!-- @component:timeline-journey-horizontal:start -->
693
693
  #### Timeline Journey Horizontal (.timeline-journey-horizontal)
694
694
 
695
- Horizontal timeline for milestones.
695
+ Horizontal milestone journey with a central axis line. Nodes sit on the axis; a dashed vertical stem leads to a tip dot, with date, title, and description text alongside. Alternate nodes above and below the axis for rhythm. Suitable for 4-8 milestones across a chronological arc, transformation story, roadmap, or multi-year programme recap.
696
696
 
697
697
  ```html
698
- <div class="timeline-journey-horizontal"><div class="timeline-node"><span>01</span><h4>Milestone</h4><p>Short note.</p></div></div>
698
+ <div class="timeline-journey-horizontal" data-preview-component="timeline-journey-horizontal">
699
+ <div class="tjh-axis"></div>
700
+
701
+ <!-- Up node: label, tip-dot, stem, axis-dot. Content grows upward. -->
702
+ <div class="tjh-item tjh-item--up" style="left:12%; --tjh-item-color:var(--accent-primary);">
703
+ <div class="tjh-label">
704
+ <span class="tjh-date">Q1</span>
705
+ <span class="tjh-title">Baseline</span>
706
+ <span class="tjh-text">Map current signals and establish the reference state.</span>
707
+ </div>
708
+ <div class="tjh-tip-dot"></div>
709
+ <div class="tjh-stem"></div>
710
+ <div class="tjh-axis-dot"></div>
711
+ </div>
712
+
713
+ <!-- Down node: axis-dot, stem, tip-dot, label. Content grows downward. -->
714
+ <div class="tjh-item tjh-item--down" style="left:34%; --tjh-item-color:var(--accent-secondary);">
715
+ <div class="tjh-axis-dot"></div>
716
+ <div class="tjh-stem"></div>
717
+ <div class="tjh-tip-dot"></div>
718
+ <div class="tjh-label">
719
+ <span class="tjh-date">Q2</span>
720
+ <span class="tjh-title">Prototype</span>
721
+ <span class="tjh-text">Convert the plan into visible experiments.</span>
722
+ </div>
723
+ </div>
724
+ </div>
699
725
  ```
700
726
 
701
727
  ```css
702
- .timeline-journey-horizontal { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px; }
703
- .timeline-journey-horizontal::before { content: ''; position: absolute; left: 0; right: 0; top: 18px; height: 1px; background: var(--line-strong); }
704
- .timeline-node { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 12px; padding-right: 18px; }
705
- .timeline-node span { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--line-strong); color: var(--accent-primary); font-size: 12px; font-weight: 800; }
706
- ```
728
+ .timeline-journey-horizontal {
729
+ --tjh-node: 12px;
730
+ --tjh-stem-h: 76px;
731
+ --tjh-col: calc(100% / 6);
732
+ position: relative;
733
+ width: 100%;
734
+ height: 340px;
735
+ }
736
+ .tjh-axis { position: absolute; top: 50%; left: 0; right: 0; height: 1px; background: var(--line-strong); transform: translateY(-50%); }
737
+ .tjh-item { position: absolute; display: flex; flex-direction: column; align-items: center; width: var(--tjh-col); transform: translateX(-50%); }
738
+ .tjh-item--up { bottom: 50%; }
739
+ .tjh-item--down { top: 50%; }
740
+ .tjh-axis-dot, .tjh-tip-dot { width: var(--tjh-node); height: var(--tjh-node); border-radius: 999px; background: var(--tjh-item-color, var(--accent-primary)); flex-shrink: 0; }
741
+ .tjh-item--up .tjh-axis-dot { margin-bottom: calc(-1 * var(--tjh-node) / 2); }
742
+ .tjh-item--down .tjh-axis-dot { margin-top: calc(-1 * var(--tjh-node) / 2); }
743
+ .tjh-stem { width: 1px; height: var(--tjh-stem-h); background-image: repeating-linear-gradient(to bottom, var(--line-strong) 0 4px, transparent 4px 8px); flex-shrink: 0; }
744
+ .tjh-label { display: flex; flex-direction: column; gap: 4px; width: 100%; padding: 0 6px; }
745
+ .tjh-item--up .tjh-label { margin-bottom: 8px; }
746
+ .tjh-item--down .tjh-label { margin-top: 8px; }
747
+ .tjh-date { font-size: var(--font-size-meta); font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tjh-item-color, var(--accent-primary)); line-height: 1.3; white-space: nowrap; }
748
+ .tjh-title { font-size: 18px; font-weight: 700; line-height: 1.15; color: var(--text-primary); }
749
+ .tjh-text { font-size: 15px; line-height: 1.45; color: var(--text-secondary); }
750
+ ```
751
+
752
+ Rules:
753
+ - Position nodes with `left: X%` inline style. For N nodes, space them at `(100 / (N + 1)) * k %` or manually distribute to show real time gaps.
754
+ - Each node may set `--tjh-item-color` inline. Prefer existing neutral theme tokens such as `--accent-primary`, `--accent-secondary`, `--accent-danger`, or a derived local accent.
755
+ - Up node DOM order is `label -> tip-dot -> stem -> axis-dot`; down node DOM order is `axis-dot -> stem -> tip-dot -> label`.
756
+ - Keep `.tjh-text` short, usually 1-2 lines. The column width limits wrapping naturally.
757
+ - Alternate up/down nodes for visual rhythm unless clustering intentionally communicates a phase.
758
+ - Adjust `--tjh-col`, `--tjh-stem-h`, and component `height` for fewer or longer milestones.
707
759
  <!-- @component:timeline-journey-horizontal:end -->
708
760
 
709
761
  <!-- @component:timeline-journey-vertical:start -->
710
762
  #### Timeline Journey Vertical (.timeline-journey-vertical)
711
763
 
712
- Vertical timeline for narrow slots.
764
+ Vertical milestone journey with a central axis line. Nodes sit on the axis; a horizontal dashed stem leads to a tip dot, with date, title, and description text alongside. Alternate nodes left and right of the axis for rhythm. Suitable for 3-8 milestones in a full-height slot.
713
765
 
714
766
  ```html
715
- <div class="timeline-journey-vertical"><div class="timeline-v-node"><span>01</span><div><h4>Milestone</h4><p>Short note.</p></div></div></div>
767
+ <div class="timeline-journey-vertical" data-preview-component="timeline-journey-vertical">
768
+ <div class="tjv-axis"></div>
769
+
770
+ <!-- Left node: DOM order stays axis-dot, stem, tip-dot, label. CSS reverses the row. -->
771
+ <div class="tjv-item tjv-item--left" style="top:18%; --tjv-item-color:var(--accent-primary);">
772
+ <div class="tjv-axis-dot"></div>
773
+ <div class="tjv-stem"></div>
774
+ <div class="tjv-tip-dot"></div>
775
+ <div class="tjv-label">
776
+ <span class="tjv-date">Discover</span>
777
+ <span class="tjv-title">Signal scan</span>
778
+ <span class="tjv-text">Collect inputs and identify the high-confidence path.</span>
779
+ </div>
780
+ </div>
781
+
782
+ <!-- Right node: same DOM order, standard row direction. -->
783
+ <div class="tjv-item tjv-item--right" style="top:42%; --tjv-item-color:var(--accent-secondary);">
784
+ <div class="tjv-axis-dot"></div>
785
+ <div class="tjv-stem"></div>
786
+ <div class="tjv-tip-dot"></div>
787
+ <div class="tjv-label">
788
+ <span class="tjv-date">Build</span>
789
+ <span class="tjv-title">Visible proof</span>
790
+ <span class="tjv-text">Create the first proof points and refine the operating model.</span>
791
+ </div>
792
+ </div>
793
+ </div>
716
794
  ```
717
795
 
718
796
  ```css
719
- .timeline-journey-vertical { display: flex; flex-direction: column; gap: 0; }
720
- .timeline-v-node { display: grid; grid-template-columns: 42px 1fr; gap: 18px; padding-bottom: 26px; position: relative; }
721
- .timeline-v-node::before { content: ''; position: absolute; left: 17px; top: 42px; bottom: 4px; width: 1px; background: var(--line-strong); }
722
- .timeline-v-node span { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--line-strong); color: var(--accent-primary); font-size: 12px; font-weight: 800; }
723
- ```
797
+ .timeline-journey-vertical {
798
+ --tjv-node: 12px;
799
+ --tjv-stem-w: 76px;
800
+ position: relative;
801
+ width: 100%;
802
+ height: 100%;
803
+ }
804
+ .tjv-axis { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--line-strong); transform: translateX(-50%); }
805
+ .tjv-item { position: absolute; display: flex; align-items: center; height: 78px; transform: translateY(-50%); }
806
+ .tjv-item--left { right: 50%; flex-direction: row-reverse; }
807
+ .tjv-item--right { left: 50%; flex-direction: row; }
808
+ .tjv-axis-dot { width: var(--tjv-node); height: var(--tjv-node); border-radius: 999px; background: var(--tjv-item-color, var(--accent-primary)); flex-shrink: 0; position: relative; z-index: 1; }
809
+ .tjv-item--left .tjv-axis-dot { margin-right: calc(-1 * var(--tjv-node) / 2); }
810
+ .tjv-item--right .tjv-axis-dot { margin-left: calc(-1 * var(--tjv-node) / 2); }
811
+ .tjv-tip-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--tjv-item-color, var(--accent-primary)); flex-shrink: 0; }
812
+ .tjv-stem { width: var(--tjv-stem-w); height: 1px; background-image: repeating-linear-gradient(to right, var(--line-strong) 0 4px, transparent 4px 8px); flex-shrink: 0; }
813
+ .tjv-label { display: flex; flex-direction: column; gap: 4px; }
814
+ .tjv-item--left .tjv-label { text-align: right; align-items: flex-end; padding-right: 18px; max-width: 440px; }
815
+ .tjv-item--right .tjv-label { text-align: left; align-items: flex-start; padding-left: 18px; max-width: 440px; }
816
+ .tjv-date { font-size: var(--font-size-meta); font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tjv-item-color, var(--accent-primary)); line-height: 1.3; white-space: nowrap; }
817
+ .tjv-title { font-size: 18px; font-weight: 700; line-height: 1.15; color: var(--text-primary); }
818
+ .tjv-text { font-size: 15px; line-height: 1.45; color: var(--text-secondary); max-width: 360px; }
819
+ ```
820
+
821
+ Rules:
822
+ - DOM order is identical for left and right nodes: `axis-dot -> stem -> tip-dot -> label`. Direction is controlled by CSS (`row-reverse` for left, `row` for right).
823
+ - Position each node with `top: Y%` inline style. For N nodes, distribute evenly with `(100 / (N + 1)) * k %` or manually to reflect time proportions.
824
+ - Each node may set `--tjv-item-color` inline. Prefer current theme tokens rather than hard-coded project colors.
825
+ - Alternate left and right nodes for rhythm. Avoid consecutive same-side nodes unless the story needs clustering.
826
+ - The parent container must have a defined height. Use `height: 100%` inside a layout slot, or set an explicit height when standalone.
827
+ - Keep `.tjv-text` to 2-3 lines. Longer labels shift the perceived center away from the axis dot.
724
828
  <!-- @component:timeline-journey-vertical:end -->
725
829
 
726
830
  <!-- @component:svg-motif:start -->
@@ -140,13 +140,37 @@
140
140
  .brand-watermark strong { font-weight: 800; color: var(--text-secondary); }
141
141
  .page-number { position: absolute; right: 34px; bottom: 26px; z-index: 10; font-size: 12px; letter-spacing: 0.14em; color: var(--text-muted); }
142
142
  .page-number--light { color: rgba(248,250,252,0.72); }
143
- .timeline-journey-horizontal { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 28px; }
144
- .timeline-journey-horizontal::before { content: ''; position: absolute; left: 0; right: 0; top: 18px; height: 1px; background: var(--line-strong); }
145
- .timeline-node { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 12px; padding-right: 18px; }
146
- .timeline-node span, .timeline-v-node span { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; background: var(--surface); border: 1px solid var(--line-strong); color: var(--accent-primary); font-size: 12px; font-weight: 800; }
147
- .timeline-journey-vertical { display: flex; flex-direction: column; gap: 0; }
148
- .timeline-v-node { display: grid; grid-template-columns: 42px 1fr; gap: 18px; padding-bottom: 26px; position: relative; }
149
- .timeline-v-node::before { content: ''; position: absolute; left: 17px; top: 42px; bottom: 4px; width: 1px; background: var(--line-strong); }
143
+ .timeline-journey-horizontal { --tjh-node: 12px; --tjh-stem-h: 62px; --tjh-col: 180px; position: relative; width: 100%; height: 245px; }
144
+ .tjh-axis { position: absolute; top: 50%; left: 0; right: 0; height: 1px; background: var(--line-strong); transform: translateY(-50%); }
145
+ .tjh-item { position: absolute; display: flex; flex-direction: column; align-items: center; width: var(--tjh-col); transform: translateX(-50%); }
146
+ .tjh-item--up { bottom: 50%; }
147
+ .tjh-item--down { top: 50%; }
148
+ .tjh-axis-dot, .tjh-tip-dot { width: var(--tjh-node); height: var(--tjh-node); border-radius: 999px; background: var(--tjh-item-color, var(--accent-primary)); flex-shrink: 0; }
149
+ .tjh-item--up .tjh-axis-dot { margin-bottom: calc(-1 * var(--tjh-node) / 2); }
150
+ .tjh-item--down .tjh-axis-dot { margin-top: calc(-1 * var(--tjh-node) / 2); }
151
+ .tjh-stem { width: 1px; height: var(--tjh-stem-h); background-image: repeating-linear-gradient(to bottom, var(--line-strong) 0 4px, transparent 4px 8px); flex-shrink: 0; }
152
+ .tjh-label { display: flex; flex-direction: column; gap: 3px; width: 100%; padding: 0 6px; }
153
+ .tjh-item--up .tjh-label { margin-bottom: 8px; }
154
+ .tjh-item--down .tjh-label { margin-top: 8px; }
155
+ .tjh-date { font-size: 11px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tjh-item-color, var(--accent-primary)); line-height: 1.3; white-space: nowrap; }
156
+ .tjh-title { font-size: 17px; font-weight: 700; line-height: 1.15; color: var(--text-primary); }
157
+ .tjh-text { font-size: 13px; line-height: 1.4; color: var(--text-secondary); }
158
+ .timeline-journey-vertical { --tjv-node: 12px; --tjv-stem-w: 46px; position: relative; width: 100%; height: 100%; min-height: 260px; }
159
+ .tjv-axis { position: absolute; left: 50%; top: 0; bottom: 0; width: 1px; background: var(--line-strong); transform: translateX(-50%); }
160
+ .tjv-item { position: absolute; display: flex; align-items: center; height: 72px; transform: translateY(-50%); }
161
+ .tjv-item--left { right: 50%; flex-direction: row-reverse; }
162
+ .tjv-item--right { left: 50%; flex-direction: row; }
163
+ .tjv-axis-dot { width: var(--tjv-node); height: var(--tjv-node); border-radius: 999px; background: var(--tjv-item-color, var(--accent-primary)); flex-shrink: 0; position: relative; z-index: 1; }
164
+ .tjv-item--left .tjv-axis-dot { margin-right: calc(-1 * var(--tjv-node) / 2); }
165
+ .tjv-item--right .tjv-axis-dot { margin-left: calc(-1 * var(--tjv-node) / 2); }
166
+ .tjv-tip-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--tjv-item-color, var(--accent-primary)); flex-shrink: 0; }
167
+ .tjv-stem { width: var(--tjv-stem-w); height: 1px; background-image: repeating-linear-gradient(to right, var(--line-strong) 0 4px, transparent 4px 8px); flex-shrink: 0; }
168
+ .tjv-label { display: flex; flex-direction: column; gap: 3px; }
169
+ .tjv-item--left .tjv-label { text-align: right; align-items: flex-end; padding-right: 14px; max-width: 220px; }
170
+ .tjv-item--right .tjv-label { text-align: left; align-items: flex-start; padding-left: 14px; max-width: 220px; }
171
+ .tjv-date { font-size: 11px; font-weight: 800; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tjv-item-color, var(--accent-primary)); line-height: 1.3; white-space: nowrap; }
172
+ .tjv-title { font-size: 17px; font-weight: 700; line-height: 1.15; color: var(--text-primary); }
173
+ .tjv-text { font-size: 13px; line-height: 1.4; color: var(--text-secondary); max-width: 210px; }
150
174
  .svg-motif { position: relative; pointer-events: none; color: var(--text-primary); }
151
175
  .svg-motif svg { display: block; width: 100%; height: 100%; overflow: visible; }
152
176
  .svg-motif--bottom { position: absolute; left: 0; right: 0; bottom: 0; height: 30%; }
@@ -156,7 +180,7 @@
156
180
  </style>
157
181
  </head>
158
182
  <body>
159
- <section class="slide" slide-qa="false" data-index="0">
183
+ <section class="slide" slide-qa="false" data-index="0" data-slide-role="cover">
160
184
  <div class="slide-canvas">
161
185
  <div class="page" style="padding:0;">
162
186
  <div class="image-title image-title--left">
@@ -229,10 +253,22 @@
229
253
  <div class="slide-canvas">
230
254
  <div class="page" style="padding:0;">
231
255
  <div class="stacked-grid">
232
- <div class="stacked-top text-panel reveal" style="padding:48px 56px 30px;"><div class="text-panel-body"><p class="eyebrow">Data and process</p><h2>Evidence components stay structural</h2><p>Charts, tables, and flows inherit the active theme while keeping predictable geometry.</p></div></div>
233
- <div class="stacked-bottom" style="display:grid;grid-template-columns:1.2fr .8fr;gap:40px;padding:0 56px 56px;">
256
+ <div class="stacked-top text-panel reveal" style="padding:42px 56px 20px;"><div class="text-panel-body"><p class="eyebrow">Data and process</p><h2>Evidence components stay structural</h2><p>Charts, tables, and journey timelines inherit the active theme while keeping predictable geometry.</p></div></div>
257
+ <div class="stacked-bottom" style="display:grid;grid-template-columns:1fr 1fr;grid-template-rows:minmax(0,1fr) 250px;gap:26px;padding:0 56px 56px;">
234
258
  <div class="echart-panel reveal"><div class="echart-panel-header"><p class="eyebrow">Example chart</p><h3>Signal distribution</h3><p class="chart-subtitle">Neutral chart defaults with restrained labels.</p></div><div class="echart-container" id="starter-chart"></div><p class="chart-caption">Source: demo data</p></div>
235
- <div class="data-table-wrap reveal"><div class="data-table-label">Component matrix</div><table class="data-table"><thead><tr><th>Primitive</th><th>Count</th><th>Role</th></tr></thead><tbody><tr><td>Layouts</td><td>6</td><td>Structure</td></tr><tr><td>Components</td><td>16</td><td>Coverage</td></tr><tr><td>SVG motif</td><td>1</td><td>Vector</td></tr></tbody></table><p class="table-caption">Starter remains visually neutral.</p></div>
259
+ <div class="timeline-journey-vertical reveal" data-preview-component="timeline-journey-vertical">
260
+ <div class="tjv-axis"></div>
261
+ <div class="tjv-item tjv-item--left" style="top:18%; --tjv-item-color:var(--accent-primary);"><div class="tjv-axis-dot"></div><div class="tjv-stem"></div><div class="tjv-tip-dot"></div><div class="tjv-label"><span class="tjv-date">Discover</span><span class="tjv-title">Signal scan</span><span class="tjv-text">Map inputs and identify the base pattern.</span></div></div>
262
+ <div class="tjv-item tjv-item--right" style="top:50%; --tjv-item-color:var(--accent-secondary);"><div class="tjv-axis-dot"></div><div class="tjv-stem"></div><div class="tjv-tip-dot"></div><div class="tjv-label"><span class="tjv-date">Build</span><span class="tjv-title">Theme draft</span><span class="tjv-text">Apply visual schema without changing structure.</span></div></div>
263
+ <div class="tjv-item tjv-item--left" style="top:82%; --tjv-item-color:var(--accent-danger);"><div class="tjv-axis-dot"></div><div class="tjv-stem"></div><div class="tjv-tip-dot"></div><div class="tjv-label"><span class="tjv-date">Verify</span><span class="tjv-title">Preview pass</span><span class="tjv-text">Check roles, components, and slide geometry.</span></div></div>
264
+ </div>
265
+ <div class="timeline-journey-horizontal reveal" data-preview-component="timeline-journey-horizontal" style="grid-column:1 / -1;">
266
+ <div class="tjh-axis"></div>
267
+ <div class="tjh-item tjh-item--up" style="left:14%; --tjh-item-color:var(--accent-primary);"><div class="tjh-label"><span class="tjh-date">01</span><span class="tjh-title">Brief</span><span class="tjh-text">Clarify source style.</span></div><div class="tjh-tip-dot"></div><div class="tjh-stem"></div><div class="tjh-axis-dot"></div></div>
268
+ <div class="tjh-item tjh-item--down" style="left:38%; --tjh-item-color:var(--accent-secondary);"><div class="tjh-axis-dot"></div><div class="tjh-stem"></div><div class="tjh-tip-dot"></div><div class="tjh-label"><span class="tjh-date">02</span><span class="tjh-title">Tokens</span><span class="tjh-text">Set color and type.</span></div></div>
269
+ <div class="tjh-item tjh-item--up" style="left:62%; --tjh-item-color:var(--accent-danger);"><div class="tjh-label"><span class="tjh-date">03</span><span class="tjh-title">Modules</span><span class="tjh-text">Skin components.</span></div><div class="tjh-tip-dot"></div><div class="tjh-stem"></div><div class="tjh-axis-dot"></div></div>
270
+ <div class="tjh-item tjh-item--down" style="left:86%; --tjh-item-color:var(--text-muted);"><div class="tjh-axis-dot"></div><div class="tjh-stem"></div><div class="tjh-tip-dot"></div><div class="tjh-label"><span class="tjh-date">04</span><span class="tjh-title">Validate</span><span class="tjh-text">Inspect preview.</span></div></div>
271
+ </div>
236
272
  </div>
237
273
  </div>
238
274
  <div class="page-number">05</div>
@@ -240,7 +276,7 @@
240
276
  </div>
241
277
  </section>
242
278
 
243
- <section class="slide" slide-qa="false" data-index="5">
279
+ <section class="slide" slide-qa="false" data-index="5" data-slide-role="closing">
244
280
  <div class="slide-canvas">
245
281
  <div class="page">
246
282
  <div class="svg-motif svg-motif--corner" aria-hidden="true"><svg viewBox="0 0 600 360"><rect x="80" y="190" width="360" height="34" fill="#dbeafe"/><circle cx="190" cy="150" r="82" fill="#3b82f6" opacity=".82"/><path d="M286 230 C330 120 474 122 516 226 C458 274 348 276 286 230 Z" fill="#64748b" opacity=".68"/><path d="M112 70 l22 -22 l22 22 l-22 22 Z" fill="none" stroke="#17191c" stroke-width="8"/><path d="M470 72 C508 38 548 46 566 88" fill="none" stroke="#17191c" stroke-width="9" stroke-linecap="round"/></svg></div>
@@ -79,6 +79,12 @@ const VISUAL_QUALITY_RULES = `Visual extraction and CSS quality rules:
79
79
  - For SVG motifs: set a viewBox, keep all eyes/mouths/decorations inside that coordinate system, and document intended placement/scale in the component notes.
80
80
  - Before saving, review the preview for text overlap, scale drift, lost anchoring, overflow, and whether the preview preserves the reference composition.`
81
81
 
82
+ const PREVIEW_REQUIREMENTS = `Preview requirements:
83
+ - \`preview.html\` must include a cover slide and a closing slide. Mark their \`<section class="slide">\` elements with \`data-slide-role="cover"\` and \`data-slide-role="closing"\`.
84
+ - \`preview.html\` must showcase every \`@component:*\` defined in \`DESIGN.md\`. Mark each showcased component with \`data-preview-component="<component-name>"\`.
85
+ - Do not save with \`revela-designs-author\` until every component has a corresponding preview marker. If a component is decorative or abstract, include a visible labeled sample state.
86
+ - When the design supports chart styling, \`preview.html\` should include a 3x3 ECharts gallery with at least 9 chart examples. This is a preview quality requirement, not a validation blocker.`
87
+
82
88
  export function buildDesignsNewPrompt({ name, base }: DesignsNewArgs): string {
83
89
  return `You are creating a new Revela visual design package.
84
90
 
@@ -107,6 +113,8 @@ You must replace unless the user explicitly requests otherwise:
107
113
 
108
114
  ${VISUAL_QUALITY_RULES}
109
115
 
116
+ ${PREVIEW_REQUIREMENTS}
117
+
110
118
  Workflow:
111
119
  1. Do not generate or save files immediately.
112
120
  2. Interview the user first. Ask for visual references such as screenshots/images, webpage URLs, text descriptions, brands, or decks they like.
@@ -126,6 +134,8 @@ Hard requirements:
126
134
  - \`DESIGN.md\` must include at least \`@design:foundation\`, \`@design:rules\`, one layout, and one component.
127
135
  - \`preview.html\` must be self-contained and directly openable in a browser.
128
136
  - Every preview slide must include \`slide-qa="true"\` or \`slide-qa="false"\`.
137
+ - \`preview.html\` must include \`data-slide-role="cover"\` and \`data-slide-role="closing"\` on slide sections.
138
+ - \`preview.html\` must showcase every \`@component:*\` with \`data-preview-component="<component-name>"\` before saving.
129
139
  - Do not save anything until the user confirms the brief.
130
140
 
131
141
  Start now by interviewing the user. Keep the first question concise.`
@@ -144,6 +154,8 @@ Goal:
144
154
 
145
155
  ${VISUAL_QUALITY_RULES}
146
156
 
157
+ ${PREVIEW_REQUIREMENTS}
158
+
147
159
  Workflow:
148
160
  1. Do not save files immediately.
149
161
  2. Ask the user what they want to change. Accept text descriptions, screenshots/images, webpage URLs, or specific complaints about the current preview.
@@ -161,6 +173,8 @@ Hard requirements:
161
173
  - Preserve at least \`@design:foundation\`, \`@design:rules\`, one layout, and one component.
162
174
  - \`preview.html\` must be self-contained and directly openable in a browser.
163
175
  - Every preview slide must include \`slide-qa="true"\` or \`slide-qa="false"\`.
176
+ - \`preview.html\` must include \`data-slide-role="cover"\` and \`data-slide-role="closing"\` on slide sections.
177
+ - \`preview.html\` must showcase every \`@component:*\` with \`data-preview-component="<component-name>"\` before saving.
164
178
  - Do not save anything until the user confirms the edit brief.
165
179
 
166
180
  Start now by asking what the user wants to change in \`${name}\`.`
@@ -250,6 +250,20 @@ export function createDesignPackage(args: CreateDesignPackageArgs): CreateDesign
250
250
  }
251
251
  }
252
252
 
253
+ function hasDataAttribute(html: string, attr: string, value: string): boolean {
254
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
255
+ return new RegExp(`${attr}\\s*=\\s*(["'])${escaped}\\1`).test(html)
256
+ }
257
+
258
+ function hasSlideRole(html: string, role: string): boolean {
259
+ const sectionRe = /<section\b[^>]*class\s*=\s*(["'])[^"']*\bslide\b[^"']*\1[^>]*>/gi
260
+ let match: RegExpExecArray | null
261
+ while ((match = sectionRe.exec(html)) !== null) {
262
+ if (hasDataAttribute(match[0], "data-slide-role", role)) return true
263
+ }
264
+ return false
265
+ }
266
+
253
267
  /** Validate a local design package for the minimum Revela design contract. */
254
268
  export function validateDesignPackage(nameInput: string): ValidateDesignPackageResult {
255
269
  let name = nameInput
@@ -298,6 +312,12 @@ export function validateDesignPackage(nameInput: string): ValidateDesignPackageR
298
312
  if (!preview.includes('<section class="slide"')) errors.push("preview.html must include slide sections")
299
313
  if (!preview.includes("slide-qa=")) errors.push("preview.html slides must include slide-qa attributes")
300
314
  if (!preview.includes("slide-canvas")) errors.push("preview.html must include .slide-canvas")
315
+ if (!hasSlideRole(preview, "cover")) errors.push('preview.html must include a slide section with data-slide-role="cover"')
316
+ if (!hasSlideRole(preview, "closing")) errors.push('preview.html must include a slide section with data-slide-role="closing"')
317
+ const missingComponents = components.filter((component) => !hasDataAttribute(preview, "data-preview-component", component))
318
+ if (missingComponents.length > 0) {
319
+ errors.push(`preview.html must showcase every @component; missing: ${missingComponents.join(", ")}`)
320
+ }
301
321
  }
302
322
 
303
323
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",