From 35f8eb80ef4a5d65b1261be9f3ea058796b5b2c9 Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Wed, 5 Aug 2026 12:01:51 +0800 Subject: [PATCH] feat: turn Ebox playground into layout gallery Replace the bare fixture with a 720 px three-section Grid and Flex reference, add paired design documentation, and lock the compact-window width contract.\n\nVerified with make check and a single-window GUI capture at a 784 px body width; rendered content stayed at 720 px. --- DESIGN.md | 82 ++++++++++++++++++++ DESIGN.zh-CN.md | 82 ++++++++++++++++++++ README.md | 4 +- README.zh-CN.md | 4 +- ebox-playground.el | 136 +++++++++++++++++++++++++++++---- tests/ebox-playground-tests.el | 21 ++++- 6 files changed, 310 insertions(+), 19 deletions(-) create mode 100644 DESIGN.md create mode 100644 DESIGN.zh-CN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..5be1895 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,82 @@ +# Design + +## Source of truth +- Status: Active +- Last refreshed: 2026-08-05 +- Primary product surfaces: The buffer opened by `ebox-playground-open` and its public layout example tree. +- Evidence reviewed: `ebox-playground.el`, `tests/ebox-playground-tests.el`, `README.md`, `README.zh-CN.md`, the rendered current example, and `../emacs-box/examples/playground/flex-reference.ebox` rendered fullscreen in GUI Emacs. + +## Brand +- Personality: Precise, modern, restrained, and educational. +- Trust signals: Pixel-aligned sections, exact Grid placement, explicit text contrast, public-only construction, and a layout that remains legible without theme-specific faces. +- Avoid: Bare unstyled fixture text, low-contrast pastel typography, arbitrary rainbow colors, oversized blank canvas, and private diagnostic internals. + +## Product goals +- Goals: Demonstrate Ebox Grid and composition in a polished standalone example; give the low-level package a visual quality bar consistent with the Flex reference. +- Non-goals: ETAF Components, application state, browser imitation, or a second Playground framework. +- Success signals: The first frame clearly communicates hierarchy and Grid behavior; every tinted block has readable text; no content crosses the visible viewport. + +## Personas and jobs +- Primary personas: Ebox authors and maintainers. +- User jobs: Open one buffer, understand the public node contract, and see fixed/fractional tracks, placement, and composition rendered attractively. +- Key contexts of use: GUI Emacs, fullscreen demonstrations, and ERT rendering checks. + +## Information architecture +- Primary navigation: None; this is one self-contained reference surface. +- Core routes/screens: One header and three capability sections. +- Content hierarchy: Title and description, labeled capability bands, then small contrasting examples. + +## Design principles +- Principle 1: Mirror the Flex reference's title—explanation—example rhythm. +- Principle 2: Use one restrained semantic accent per capability section. +- Principle 3: Keep the example public-only and small enough to understand from one source file. +- Tradeoffs: Prefer a compact representative gallery over exhaustive property coverage. + +## Visual language +- Color: Warm paper canvas with terracotta, sage, slate blue, and violet section families; explicit dark ink or white text according to contrast. +- Typography: Configured monospace, bold only for titles and short labels. +- Spacing/layout rhythm: One-line vertical gaps, 12 px horizontal gaps, 16–24 px section padding. +- Shape/radius/elevation: Square one-pixel borders; no shadows or fake radius. +- Motion: None. +- Imagery/iconography: Text-only. + +## Components +- Existing components to reuse: `ebox-create`, `ebox-column`, `ebox-row`, `ebox-flex`, `ebox-grid`, and `ebox-spacer`. +- New/changed components: Header band, capability section band, fixed/fractional track cards, placement cards, and public-contract footer. +- Variants and states: Static semantic color families only. +- Token/component ownership: Palette constants and example composition remain in `ebox-playground.el`; Ebox owns rendering. + +## Accessibility +- Target standard: High contrast and readable labels in ordinary GUI Emacs themes. +- Keyboard/focus behavior: No interactive controls in this static example. +- Contrast/readability: Explicit foreground on every tinted surface. +- Screen-reader semantics: Plain descriptive text remains present in the buffer. +- Reduced motion and sensory considerations: No motion. + +## Responsive behavior +- Supported breakpoints/devices: GUI Emacs windows with at least 760 px of body width, from compact frames to fullscreen desktop widths. +- Layout adaptations: The gallery owns one fixed 720 px content canvas; nested Grid and Flex sections divide that width through public Ebox layout behavior instead of reading the viewport independently. +- Touch/hover differences: None. + +## Interaction states +- Loading: Not applicable. +- Empty: Not applicable. +- Error: Public Ebox errors surface normally. +- Success: The rendered gallery itself is the success state. +- Disabled: Not applicable. +- Offline/slow network, if applicable: Not applicable. + +## Content voice +- Tone: Concise, factual, and self-explaining. +- Terminology: Ebox, node, Grid, fixed track, fractional track, placement, public API. +- Microcopy rules: Explain what a visible section proves in one sentence. + +## Implementation constraints +- Framework/styling system: Emacs 29.1+ and public Ebox constructors/properties only. +- Design-token constraints: Reuse the Flex reference's restrained color families without introducing a theme package. +- Performance constraints: One synchronous render, no timers or background work. +- Compatibility constraints: No ETAF dependency and no `ebox--*` calls. +- Test/screenshot expectations: `make check` passes and a fullscreen one-window screenshot shows complete, unclipped labeled sections. + +## Open questions +- [ ] Add more standalone examples only when each teaches a distinct public Ebox responsibility / maintainer / prevents fixture sprawl. diff --git a/DESIGN.zh-CN.md b/DESIGN.zh-CN.md new file mode 100644 index 0000000..e78001a --- /dev/null +++ b/DESIGN.zh-CN.md @@ -0,0 +1,82 @@ +# 设计 + +## 真相来源 +- 状态:有效 +- 最近更新:2026-08-05 +- 主要产品界面:`ebox-playground-open` 打开的缓冲区及其公开布局示例树。 +- 已审阅证据:`ebox-playground.el`、`tests/ebox-playground-tests.el`、`README.md`、`README.zh-CN.md`、当前示例的实际渲染,以及在 GUI Emacs 中全屏渲染的 `../emacs-box/examples/playground/flex-reference.ebox`。 + +## 品牌 +- 个性:精确、现代、克制、适合教学。 +- 信任信号:像素对齐的分区、准确的 Grid 放置、明确的文字对比度、只使用公开 API,以及不依赖主题 face 也清晰的布局。 +- 避免:没有样式的裸 fixture 文本、低对比度的浅色文字、随意的彩虹配色、过大的空白画布,以及私有诊断接口。 + +## 产品目标 +- 目标:用一个精致的独立示例演示 Ebox Grid 与组合能力,让底层包达到 Flex 参考示例的视觉质量标准。 +- 非目标:ETAF Component、应用状态、浏览器仿制品,或第二套 Playground 框架。 +- 成功信号:第一屏能清楚表达层级和 Grid 行为;每个带色区块的文字都清晰可读;没有内容越过可见视口。 + +## 用户与任务 +- 主要用户:Ebox 作者和维护者。 +- 用户任务:打开一个缓冲区,理解公开节点契约,并直观看到固定/分数轨道、显式放置和组合渲染。 +- 主要使用场景:GUI Emacs、全屏演示和 ERT 渲染检查。 + +## 信息架构 +- 主导航:无;这是一个自包含的参考界面。 +- 核心界面:一个标题区和三个能力分区。 +- 内容层级:标题与说明、带编号的能力标题带、随后是小型对比示例。 + +## 设计原则 +- 原则 1:延续 Flex 参考示例的“标题—解释—示例”节奏。 +- 原则 2:每个能力分区只使用一组克制的语义强调色。 +- 原则 3:示例只使用公开 API,并保持在一个源文件内即可理解的规模。 +- 取舍:优先紧凑且有代表性的画廊,而不是穷举所有属性。 + +## 视觉语言 +- 颜色:暖纸色画布,搭配陶土色、鼠尾草绿、灰蓝色和紫色分区;按对比度明确设置深色或白色文字。 +- 字体:使用当前等宽字体,只在标题和短标签中加粗。 +- 间距与布局节奏:一行垂直间隔、12 px 水平间隔、16–24 px 分区内边距。 +- 形状/圆角/层级:方形一像素边框;不使用阴影或伪圆角。 +- 动效:无。 +- 图像与图标:纯文本。 + +## 组件 +- 复用的现有组件:`ebox-create`、`ebox-column`、`ebox-row`、`ebox-flex`、`ebox-grid` 和 `ebox-spacer`。 +- 新增/调整的组件:标题带、能力分区标题带、固定/分数轨道卡片、放置卡片和公开契约页脚。 +- 变体与状态:仅使用静态语义色组。 +- Token/组件归属:配色常量和示例组合保留在 `ebox-playground.el`;Ebox 负责渲染。 + +## 无障碍 +- 目标标准:在常见 GUI Emacs 主题下保持高对比度和标签可读性。 +- 键盘/焦点行为:该静态示例没有交互控件。 +- 对比度/可读性:每个带色表面都显式设置前景色。 +- 屏幕阅读语义:描述性纯文本保留在缓冲区中。 +- 减少动态与感官刺激:无动态效果。 + +## 响应式行为 +- 支持的断点/设备:正文宽度至少为 760 px 的 GUI Emacs 窗口,从紧凑窗口到全屏桌面。 +- 布局适配:画廊统一拥有一个固定的 720 px 内容画布;嵌套 Grid 和 Flex 通过公开 Ebox 布局行为划分该宽度,而不是各自读取视口。 +- 触控/悬停差异:无。 + +## 交互状态 +- 加载:不适用。 +- 空状态:不适用。 +- 错误:公开 Ebox 错误正常向外暴露。 +- 成功:完整渲染画廊本身就是成功状态。 +- 禁用:不适用。 +- 离线/慢网络:不适用。 + +## 内容语气 +- 语气:简洁、事实明确、自解释。 +- 术语:Ebox、节点、Grid、固定轨道、分数轨道、放置、公开 API。 +- 微文案规则:用一句话说明每个可见分区证明了什么。 + +## 实现约束 +- 框架/样式系统:Emacs 29.1+,只使用公开 Ebox 构造函数和属性。 +- 设计 Token 约束:复用 Flex 参考示例的克制色系,不引入主题包。 +- 性能约束:单次同步渲染,不使用定时器或后台任务。 +- 兼容性约束:不依赖 ETAF,不调用 `ebox--*`。 +- 测试/截图要求:`make check` 通过;单窗口 GUI 截图显示完整且未裁剪的标签分区。 + +## 开放问题 +- [ ] 只有当每个新增示例都能讲清一种独立的公开 Ebox 责任时才继续扩展示例,避免 fixture 膨胀。 diff --git a/README.md b/README.md index 03a57f9..4260989 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # ebox-playground -`ebox-playground` is the independent developer example package for Ebox. It uses only public Ebox constructors and demonstrates fixed, fractional, two-dimensional, and placed layout. It is separate from `etaf-playground`, which demonstrates the higher-level Component and Runtime framework. +`ebox-playground` is the independent developer example package for Ebox. It uses only public Ebox constructors and presents a polished three-part gallery for fixed and fractional tracks, explicit Grid placement, and public Column/Flex/Grid composition. Its restrained terracotta, sage, blue, and violet palette follows the visual standard of the original Flex reference without depending on theme-specific faces. + +The gallery owns one definite 720 px canvas so its nested Grid examples cannot each reinterpret the surrounding viewport width. It fits a compact GUI Emacs window with at least 760 px of body width. `etaf-playground` remains the separate higher-level Component and Runtime showcase. ```elisp (require 'ebox-playground) diff --git a/README.zh-CN.md b/README.zh-CN.md index 60505ff..d9fc795 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,6 +1,8 @@ # ebox-playground -`ebox-playground` 是 Ebox 的独立开发示例包,只使用 Ebox 公共构造函数,展示固定轨道、分数轨道、二维 Grid 和显式放置。它与展示高层 Component/Runtime 框架的 `etaf-playground` 分开。 +`ebox-playground` 是 Ebox 的独立开发示例包,只使用 Ebox 公共构造函数,并通过一套精心设计的三段式画廊展示固定与分数轨道、显式 Grid 放置,以及公开的 Column/Flex/Grid 组合能力。克制的陶土色、鼠尾草绿、蓝色与紫色配色延续了原 Flex 参考示例的视觉标准,同时不依赖特定主题 face。 + +画廊统一拥有一个明确的 720 px 画布,因此嵌套 Grid 不会各自重新解释外层视口宽度。只要 GUI Emacs 的正文区域不小于 760 px,它就能完整显示。高层 Component/Runtime 展示仍由独立的 `etaf-playground` 提供。 ```elisp (require 'ebox-playground) diff --git a/ebox-playground.el b/ebox-playground.el index a678158..992b1e9 100644 --- a/ebox-playground.el +++ b/ebox-playground.el @@ -19,22 +19,132 @@ (defconst ebox-playground-buffer-name "*Ebox Playground*" "Default buffer name used by `ebox-playground-open'.") +(defun ebox-playground--header () + "Return the gallery header." + (ebox-create + :content "Ebox Playground\nPixel-precise layout, composed only from public nodes." + :width '(720) + :padding '(1 (18)) + :border "#8F432F" + :color "#252A2E" + :bgcolor "#FFFDF8" + :face 'bold + :text-align 'center)) + +(defun ebox-playground--section-heading + (number title copy border background) + "Return a section heading for NUMBER, TITLE, COPY, BORDER, and BACKGROUND." + (ebox-create + :content (format "%s %s\n%s" number title copy) + :width '(720) + :padding '(0 (16)) + :border border + :border-bottom-p nil + :color "#252A2E" + :bgcolor background + :wrap-mode 'word)) + +(defun ebox-playground--track-grid () + "Return the fixed and fractional track example." + (ebox-grid + :width '(718) + :grid-template-columns '((220) 1fr) + :gap '(1 (12)) + :border "#C97252" + :bgcolor "#FFF9F5" + (ebox-create :content "FIXED TRACK\n220 px stays exact" + :width '(220) :padding '(1 0) :text-align 'center + :color "#252A2E" :bgcolor "#F1D4C9") + (ebox-create :content "FRACTIONAL TRACK\n1fr receives the remaining width" + :width '(486) :padding '(1 0) :text-align 'center + :color "#252A2E" :bgcolor "#F9E8E0") + (ebox-create :content "The Grid owns one definite width; its tracks divide that space without competing viewport calculations." + :grid-column '(1 :span 2) :width '(718) :padding '(1 0) + :color "#663322" :bgcolor "#FFFDF8" + :text-align 'center :wrap-mode 'word))) + +(defun ebox-playground--placement-grid () + "Return the explicit Grid placement example." + (ebox-grid + :width '(718) + :grid-template-columns '((210) (210) 1fr) + :gap '(1 (10)) + :border "#6D8A73" + :bgcolor "#F7FBF7" + (ebox-create :content "SPAN 1 → 3" :grid-column '(1 :span 2) + :width '(430) :padding '(1 0) :text-align 'center + :color "#24422D" :bgcolor "#DCEBDD") + (ebox-create :content "COLUMN 3" :grid-column 3 :grid-row 1 + :width '(278) :padding '(1 0) :text-align 'center + :color "#24422D" :bgcolor "#C8DDCB") + (ebox-create :content "ROW 2 / COL 1" :grid-column 1 :grid-row 2 + :width '(210) :padding '(1 0) :text-align 'center + :color "#24422D" :bgcolor "#EDF5EE") + (ebox-create :content "AUTO PLACED" :width '(210) :padding '(1 0) + :text-align 'center + :color "#24422D" :bgcolor "#E3F0E5") + (ebox-create :content "STABLE CELLS" :width '(278) :padding '(1 0) + :text-align 'center + :color "#24422D" :bgcolor "#D4E6D7"))) + +(defun ebox-playground--composition-flex () + "Return the public node composition example." + (ebox-flex + :width '(720) + :flex-flow '(row wrap) + :gap '(1 (12)) + :padding '(1 (12)) + :border "#4E7890" + :bgcolor "#F7FAFC" + (ebox-create :content "COLUMN\nvertical composition" + :flex-grow 1 :flex-shrink 1 :flex-basis '(200) + :min-width '(180) :padding '(1 (12)) + :color "#193846" :bgcolor "#D9EAF2") + (ebox-create :content "FLEX\nwrap and distribute" + :flex-grow 1 :flex-shrink 1 :flex-basis '(200) + :min-width '(180) :padding '(1 (12)) + :color "#3F3655" :bgcolor "#E7E2F1") + (ebox-create :content "GRID\nplace in two dimensions" + :flex-grow 1 :flex-shrink 1 :flex-basis '(200) + :min-width '(180) :padding '(1 (12)) + :color "#6B3020" :bgcolor "#F1D4C9"))) + +(defun ebox-playground--footer () + "Return the public-contract footer." + (ebox-create + :content "PUBLIC CONTRACT ebox-create · ebox-column · ebox-flex · ebox-grid · ebox-spacer" + :width '(720) + :padding '(0 (16)) + :border "#8D887F" + :color "#4D5651" + :bgcolor "#EEEAE2" + :wrap-mode 'word)) + ;;;###autoload (defun ebox-playground-view () "Return a public Ebox example layout node without buffer side effects." - (ebox-grid - :width '(640) - :grid-template-columns '((200) 1fr) - :grid-template-rows '(1 1 1) - :gap '(1 (12)) - :padding '(1 2) - :border '((1) solid "#687386") - (ebox-create :content "Ebox Playground" :face 'bold) - (ebox-create :content "A pixel-precise two-dimensional layout") - (ebox-create :content "Row / column / flex / grid share the public node contract") - (ebox-create :content "Fixed and fractional tracks") - (ebox-create :content "Placement" :grid-column 1 :grid-row 3) - (ebox-create :content "No private calls" :grid-column 2 :grid-row 3))) + (ebox-column + (ebox-playground--header) + (ebox-spacer :height 1) + (ebox-playground--section-heading + "01" "FIXED + FRACTIONAL" + "A definite Grid width divides into one exact track and one flexible track." + "#C97252" "#F1D4C9") + (ebox-playground--track-grid) + (ebox-spacer :height 1) + (ebox-playground--section-heading + "02" "EXPLICIT PLACEMENT" + "Span columns, target cells, and leave the remaining nodes to auto placement." + "#6D8A73" "#DCEBDD") + (ebox-playground--placement-grid) + (ebox-spacer :height 1) + (ebox-playground--section-heading + "03" "PUBLIC COMPOSITION" + "Column, Flex, and Grid share the same public node contract." + "#4E7890" "#D9EAF2") + (ebox-playground--composition-flex) + (ebox-spacer :height 1) + (ebox-playground--footer))) ;;;###autoload (defun ebox-playground-open (&optional buffer-name) diff --git a/tests/ebox-playground-tests.el b/tests/ebox-playground-tests.el index 484185b..5b7fe27 100644 --- a/tests/ebox-playground-tests.el +++ b/tests/ebox-playground-tests.el @@ -5,10 +5,12 @@ (ert-deftest ebox-playground-uses-only-public-layout-apis () "The example should build and render through the public Ebox entry point." - (let ((node (ebox-playground-view))) - (should (eq (plist-get node :ebox-type) 'grid)) - (should (string-match-p "Ebox Playground" - (substring-no-properties (ebox-render node)))))) + (let* ((node (ebox-playground-view)) + (rendered (substring-no-properties (ebox-render node)))) + (should (eq (plist-get node :ebox-type) 'stack)) + (dolist (label '("Ebox Playground" "FIXED + FRACTIONAL" + "EXPLICIT PLACEMENT" "PUBLIC COMPOSITION")) + (should (string-match-p (regexp-quote label) rendered))))) (ert-deftest ebox-playground-opens-and-closes-buffer () "The public open and close commands should own their buffer lifecycle." @@ -21,6 +23,17 @@ (should (string-match-p "Ebox Playground" (buffer-string))))) (ebox-playground-close name)))) +(ert-deftest ebox-playground-fits-a-compact-gui-window () + "Keep the gallery wide enough to teach Grid without crossing 800 px frames." + (let* ((node (ebox-playground-view)) + (gallery-width (plist-get (car (plist-get node :children)) :width)) + (rendered (ebox-render node)) + (widths (mapcar #'ebox-string-pixel-width + (ebox-string-lines rendered))) + (maximum (apply #'max widths))) + (should (= maximum gallery-width)) + (should (<= gallery-width 760)))) + (provide 'ebox-playground-tests) ;;; ebox-playground-tests.el ends here