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.
This commit is contained in:
Kinneyzhang 2026-08-05 12:01:51 +08:00
parent a63dd61f78
commit 35f8eb80ef
6 changed files with 310 additions and 19 deletions

82
DESIGN.md Normal file
View File

@ -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, 1624 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.

82
DESIGN.zh-CN.md Normal file
View File

@ -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 水平间隔、1624 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 膨胀。

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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