@cyber-dash-tech/revela 0.7.7 → 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 +10 -5
- package/README.zh-CN.md +10 -5
- package/designs/starter/DESIGN.md +118 -14
- package/designs/starter/preview.html +48 -12
- package/lib/commands/designs-new.ts +14 -0
- package/lib/commands/edit.ts +0 -4
- package/lib/commands/help.ts +1 -1
- package/lib/design/designs.ts +20 -0
- package/lib/edit/resolve-deck.ts +27 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,7 +140,7 @@ Disable presentation mode when done:
|
|
|
140
140
|
/revela init initialize or refresh workspace DECKS.json
|
|
141
141
|
/revela review [slug] review active deck readiness before writing HTML
|
|
142
142
|
/revela remember <text> save an explicit user/workflow preference
|
|
143
|
-
/revela edit
|
|
143
|
+
/revela edit [target] open visual editor for active deck or a specific target
|
|
144
144
|
|
|
145
145
|
/revela designs list installed designs
|
|
146
146
|
/revela designs <name> activate a design
|
|
@@ -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
|
|
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
|
-
-
|
|
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
|
|
|
@@ -591,13 +593,16 @@ A custom domain is a folder containing `INDUSTRY.md`.
|
|
|
591
593
|
|
|
592
594
|
## Visual Editing
|
|
593
595
|
|
|
594
|
-
Open the visual editor for
|
|
596
|
+
Open the visual editor for the active deck, or pass a slug / workspace-relative HTML path:
|
|
595
597
|
|
|
596
598
|
```text
|
|
599
|
+
/revela edit
|
|
597
600
|
/revela edit my-deck
|
|
598
601
|
/revela edit decks/my-deck.html
|
|
599
602
|
```
|
|
600
603
|
|
|
604
|
+
Without a target, `/revela edit` opens `DECKS.json.activeDeck`. If no active deck is set and there is exactly one deck in `DECKS.json`, it opens that deck.
|
|
605
|
+
|
|
601
606
|
The editor opens in your browser. Use `Ctrl`/`Cmd` + click to reference deck elements, write a natural-language comment, then send it back to OpenCode. Revela sends a structured edit prompt that includes the deck file, slide context, selected element metadata, and your comment.
|
|
602
607
|
|
|
603
608
|
LLM tool equivalent: `revela-edit` with `{ "target": "decks/my-deck.html" }`. This lets the agent open the same editor when you say things like “I want to edit @decks/my-deck.html”.
|
package/README.zh-CN.md
CHANGED
|
@@ -139,7 +139,7 @@ Create a 6-slide HTML deck on humanoid robotics supply chains. Cite the main mar
|
|
|
139
139
|
/revela init 初始化或刷新工作区 DECKS.json
|
|
140
140
|
/revela review [slug] 写 HTML 前检查 active deck readiness
|
|
141
141
|
/revela remember <text> 保存明确的用户/工作流偏好
|
|
142
|
-
/revela edit
|
|
142
|
+
/revela edit [target] 打开 active deck 或指定 target 的可视化编辑器
|
|
143
143
|
|
|
144
144
|
/revela designs 列出已安装 design
|
|
145
145
|
/revela designs <name> 激活某个 design
|
|
@@ -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
|
|
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
|
-
-
|
|
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
|
|
|
@@ -556,13 +558,16 @@ Prompt 注入规则:
|
|
|
556
558
|
|
|
557
559
|
## 可视化编辑
|
|
558
560
|
|
|
559
|
-
|
|
561
|
+
可以直接打开 active deck,也可以传入 deck slug 或工作区相对 HTML 路径:
|
|
560
562
|
|
|
561
563
|
```text
|
|
564
|
+
/revela edit
|
|
562
565
|
/revela edit my-deck
|
|
563
566
|
/revela edit decks/my-deck.html
|
|
564
567
|
```
|
|
565
568
|
|
|
569
|
+
不传 target 时,`/revela edit` 会打开 `DECKS.json.activeDeck`。如果没有 active deck,但 `DECKS.json` 里只有一个 deck,则打开这个唯一 deck。
|
|
570
|
+
|
|
566
571
|
编辑器会在浏览器中打开。使用 `Ctrl`/`Cmd` + 点击 deck 元素来引用它们,写一段自然语言评论,然后发送回 OpenCode。Revela 会把 deck 文件、slide 上下文、选中元素 metadata 和你的评论整理成结构化 edit prompt。
|
|
567
572
|
|
|
568
573
|
对应的 LLM tool:`revela-edit`,参数为 `{ "target": "decks/my-deck.html" }`。因此当你说“我要编辑 @decks/my-deck.html”时,agent 也可以主动打开同一个编辑器。
|
|
@@ -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
|
|
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"
|
|
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 {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
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
|
|
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"
|
|
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 {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
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 {
|
|
144
|
-
.
|
|
145
|
-
.
|
|
146
|
-
.
|
|
147
|
-
.
|
|
148
|
-
.
|
|
149
|
-
.
|
|
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:
|
|
233
|
-
<div class="stacked-bottom" style="display:grid;grid-template-columns:
|
|
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="
|
|
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}\`.`
|
package/lib/commands/edit.ts
CHANGED
|
@@ -6,10 +6,6 @@ export async function handleEdit(
|
|
|
6
6
|
send: (text: string) => Promise<void>,
|
|
7
7
|
): Promise<void> {
|
|
8
8
|
const target = input.trim()
|
|
9
|
-
if (!target) {
|
|
10
|
-
await send("**Usage:** `/revela edit <deck-slug|decks/file.html>`\n\nExamples: `/revela edit investor-update`, `/revela edit decks/investor-update.html`")
|
|
11
|
-
return
|
|
12
|
-
}
|
|
13
9
|
|
|
14
10
|
try {
|
|
15
11
|
const result = openEditableDeck(target, {
|
package/lib/commands/help.ts
CHANGED
|
@@ -28,7 +28,7 @@ export async function handleHelp(
|
|
|
28
28
|
`\`/revela disable\` — disable slide generation mode\n` +
|
|
29
29
|
`\`/revela init\` — initialize or refresh workspace DECKS.json\n` +
|
|
30
30
|
`\`/revela review [slug]\` — review active deck readiness before writing HTML\n` +
|
|
31
|
-
`\`/revela edit
|
|
31
|
+
`\`/revela edit [target]\` — open visual editor for active deck or a specific target\n` +
|
|
32
32
|
`\`/revela remember <text>\` — save an explicit preference to DECKS.json\n` +
|
|
33
33
|
`\`/revela designs\` — list installed designs\n` +
|
|
34
34
|
`\`/revela designs <name>\` — activate a design\n` +
|
package/lib/design/designs.ts
CHANGED
|
@@ -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/lib/edit/resolve-deck.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface EditableDeck {
|
|
|
11
11
|
|
|
12
12
|
export function resolveEditableDeck(workspaceRoot: string, input: string): EditableDeck {
|
|
13
13
|
const requested = input.trim()
|
|
14
|
-
if (!requested)
|
|
14
|
+
if (!requested) return resolveDefaultDeck(workspaceRoot)
|
|
15
15
|
|
|
16
16
|
const slug = normalizeSlug(requested)
|
|
17
17
|
|
|
@@ -32,6 +32,32 @@ export function resolveEditableDeck(workspaceRoot: string, input: string): Edita
|
|
|
32
32
|
return resolveDeckFile(workspaceRoot, slug, `decks/${slug}.html`, "fallback")
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function resolveDefaultDeck(workspaceRoot: string): EditableDeck {
|
|
36
|
+
if (!hasDecksState(workspaceRoot)) {
|
|
37
|
+
throw new Error(`No ${DECKS_STATE_FILE} found. Use /revela edit <deck-slug|decks/file.html>.`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const state = readDecksState(workspaceRoot)
|
|
41
|
+
const activeSlug = normalizeSlug(state.activeDeck || "")
|
|
42
|
+
if (activeSlug) {
|
|
43
|
+
const deck = state.decks[activeSlug]
|
|
44
|
+
if (!deck) throw new Error(`Active deck ${activeSlug} does not exist in ${DECKS_STATE_FILE}. Use /revela edit <target>.`)
|
|
45
|
+
return resolveDeckFile(workspaceRoot, deck.slug, deck.outputPath, "decks-state")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const decks = Object.values(state.decks)
|
|
49
|
+
if (decks.length === 1) {
|
|
50
|
+
const deck = decks[0]
|
|
51
|
+
return resolveDeckFile(workspaceRoot, deck.slug, deck.outputPath, "decks-state")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (decks.length === 0) {
|
|
55
|
+
throw new Error(`${DECKS_STATE_FILE} has no decks. Use /revela edit <deck-slug|decks/file.html>.`)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(`${DECKS_STATE_FILE} has multiple decks and no activeDeck. Use /revela edit <target>.`)
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
function resolvePathTarget(workspaceRoot: string, requested: string): EditableDeck {
|
|
36
62
|
if (isAbsoluteLike(requested)) {
|
|
37
63
|
throw new Error("/revela edit only accepts workspace-relative decks/*.html paths.")
|