Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

本文件是 `escaping` 仓库的 coding-agent 指南。以当前代码、测试和 domain docs 为准。
架构与完整文档导航见[维护者入口](docs/dual-repo-architecture.md),主题设计见
[ADR-0008](docs/adr/0008-theme-api-3-data-presentation-split.md)。
[ADR-0008](docs/adr/0008-theme-api-3-data-presentation-split.md) 和
[ADR-0009](docs/adr/0009-site-owned-pages-redirects-and-sub-paths.md)。

## 产品与边界

`escaping` 把 GitHub Issues 变成个人网站:Home、Blog、Ideas、About、Projects、Tags、
Atom、sitemap、robots 和搜索索引。
Atom、sitemap、robots、搜索索引和旧地址跳转页。

核心分工:**生成器只管数据,主题只管呈现。**

- 生成器负责:读取 Issue、净化 HTML、分配唯一网址、生成 Atom/sitemap/搜索索引、
检查站内链接和资源、安全地替换输出目录。
- 主题负责:页面长什么样、放哪些 SEO 标签、界面文字、主题自己的选项和额外页面。
- 站点仓库负责:真实 `config.yaml`、可选的本地主题、Pages workflow 和 `CNAME`。
- 主题负责:页面长什么样、放哪些 SEO 标签、界面文字、主题自己的选项;只需
`blog.html` 和 `post.html`,其余页面按固定规则退回这两个模板。
- 站点仓库负责:真实 `config.yaml`(包括有哪些页面、地址、额外页面和旧地址跳转)、
可选的本地主题、Pages workflow 和 `CNAME`。

仓库职责:

Expand Down Expand Up @@ -46,16 +49,17 @@ Issue 是唯一内容来源,`published` 标签控制发布,内容类型只

1. **Config 分两层。** 判断一个值放哪层,只问一句:换了主题之后它还有意义吗?
有意义放站点层(`github`、`site`(含 `navigation`)、`profile`、`about`、`paths`、
`projects`、`comments`、`seo`、`security`);没意义放主题层(`theme.options`),由主题的
`pages`、`redirects`、`projects`、`comments`、`seo`、`security`);没意义放主题层(`theme.options`),由主题的
`theme.yaml` 声明类型和默认值。生成器代码里不得出现只为某个主题服务的字段。
2. **Quiet 没有特权。** Quiet 是唯一内置和默认主题,但与本地主题走同一套加载、
选项、页面和字符串机制;编译器不得按主题名分支。
3. **模板只拿到四个变量**:`site`、`page`、`theme`(选项最终值)、`t`(界面文字)。
新数据加进这四个对象,不新增顶层变量。
4. `Settings` 显式注入;禁止全局配置单例。Renderer 与 artifact validator 只读
`SiteModel` 和已加载的主题。
5. `RouteRegistry` 是唯一构造 `Route` 的地方,包括主题通过 `pages` 声明的页面。
页面持有完整 Route,不手工拼接输出路径。
5. `RouteRegistry` 是唯一构造 `Route` 的地方,包括 `pages.extra` 的页面,也负责给
子路径站点加前缀。页面持有完整 Route,不手工拼接地址或输出路径;主题自己的
地址用 `url` 过滤器。
6. Config 中的相对路径(主题、输出目录)以 Config 文件所在目录为根,不依赖 CWD。
7. `ThemeLoader` 只读取包内资源或本地目录:不联网、不缓存、不执行主题代码、
拒绝符号链接。`extends` 只能指向内置主题。
Expand All @@ -82,9 +86,9 @@ Issue 是唯一内容来源,`published` 标签控制发布,内容类型只
```text
src/escaping/
├── config.py # 站点层 Config
├── theme.py # theme.yaml API 3、extends、选项校验、字符串
├── theme.py # theme.yaml API 4、extends、模板回退、选项校验、字符串
├── content_compiler.py # Issue → Blog/Idea/About
├── site_builder.py # SiteModel、路由、主题页面
├── site_builder.py # SiteModel、路由、额外页面、跳转页
├── routes.py
├── site_compiler.py # 预检 → 拉取 → 编译 → 渲染 → 校验 → 发布
├── services/render_service.py
Expand Down
86 changes: 86 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,91 @@ All notable changes to escaping are recorded here. The format follows
[Semantic Versioning](https://semver.org/) (before 1.0, a minor version may
break things).

## [0.3.0] - 2026-09-28

The site now decides which pages it has and where they live; a Theme only
supplies templates. Old addresses and sites under a path are handled by
escaping. Local Themes need a small update: read
[Upgrading from 0.2](#upgrading-from-02).

### Added

- **`pages` in `config.yaml`.** Each section (`blog`, `ideas`, `tags`,
`projects`, `about`) takes `true`, `false` or a path, such as
`blog: /posts/` or `tags: false`. The Blog cannot be turned off. `extra`
adds pages such as `/now/` or one page per project, each rendered by a
named Theme template. See [Pages](docs/site-inputs.md#pages).
- **`redirects` in `config.yaml`.** Old addresses (ending in `/` or `.html`)
become pages that send visitors on to a current page. Chains are followed;
circles and duplicates fail the build. A redirect whose target is no page,
or whose source is a page again, is left out with the warning
`REDIRECT_LEFT_OUT`. See [Redirects](docs/site-inputs.md#redirects).
- **Sites under a path**, such as a GitHub project site at
`https://alice.github.io/notes/`. `site.url` and the Pages URL may carry a
path. Page addresses, links in Issues and root-relative Config values move
under it. See [Sites under a path](docs/site-inputs.md#sites-under-a-path).
- **Theme API 4.**
- Only `blog.html` and `post.html` are required. Home, Ideas and tag pages
fall back to `blog.html`; Ideas and About fall back to `post.html`.
`tags.html` and `projects.html` are needed only when those pages are on,
and the error names both fixes.
- Every page has the same fields: `page.item`, `page.items`,
`page.pagination`, `page.newer`, `page.older`, `page.tag`,
`page.project`, `none` when unused. Posts, Ideas and About share the
fields a single page shows.
- The `url` filter: `{{ '/assets/site.css'|url }}` works at the root of a
host and under a path.
- The Theme guide explains how to [share a Theme](docs/themes/authoring.md#sharing-a-theme).

### Changed

- **Breaking:** Themes declare `api: 4`. `pages` in `theme.yaml` is rejected
with its new place (`pages.extra` in the site's `config.yaml`).
- **Breaking:** `page.post`, `page.idea`, `page.about` and `page.archive`
are replaced; see
[Migrating from API 3](docs/themes/authoring.md#migrating-from-api-3).
- `site.routes.<name>` and `site.about` are `none` for a page that is off, and
a Blog tag's `path` is `none` when the tag pages are off.
- The default menu follows `pages`: a moved Blog is linked at its new address;
a page that is off is left out.
- A menu link to a page that is off says so, for example
`(pages.tags is false)`.
- Issues of a section that is off are not published and get the warning
`PAGE_OFF`.
- A profile About (no About Issue) has `body_html` with the bio.
- The output check reports `LINK_OUTSIDE_SITE` for a Theme address that
leaves a site under a path, and names the `url` filter.
- `robots.txt` is written only for a site at the root of a host.
- A template that prints `none` fails with its file and line instead of
writing the word None into the page.
- An About page from an Issue has an empty `created_date`: About shows no
date.
- A description taken from the body leaves out Mermaid diagram source.
- A full URL into a folder the site does not write, such as a GitHub project
site on the same host, is not checked as a page of this site. A broken link
to a page suggests adding the old address to `redirects`.
- `escpe theme check` renders enough sample posts for a second Blog page and
leaves out warnings about Issues that the Config names.
- The platform context written by the Action loses `pages_base_path`.
- Quiet uses the `url` filter and hides links to pages that are off.

### Upgrading from 0.2

1. **Pin the new version.** Change `uses: geoqiao/escaping@v0.2.0` to
`@v0.3.0` (or its commit SHA) in the site workflow.
2. **Quiet only:** nothing else to do.
3. **A local Theme:** set `api: 4` and follow
[Migrating from API 3](docs/themes/authoring.md#migrating-from-api-3).
Move any `pages:` from `theme.yaml`, unchanged, to `pages.extra` in
`config.yaml`.
4. **A site script that wrote redirect pages** after the Action step: move
its map into `redirects` and delete the script and its workflow step.
5. **Check locally (optional).** `escpe theme check --config config.yaml`.

To roll back, pin 0.2.0 again and restore the Theme's `api: 3` and `pages`
together with the old Config; 0.2 rejects `pages` and `redirects` in
`config.yaml`.

## [0.2.0] - 2026-09-28

This release separates data from presentation. The generator turns Issues and
Expand Down Expand Up @@ -160,5 +245,6 @@ and 0.1 has no Action to pin.
First release: Blog, Ideas, About, Projects, Tags, Atom, sitemap and search
from GitHub Issues, with the Quiet Theme and the starter workflow.

[0.3.0]: https://github.com/geoqiao/escaping/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/geoqiao/escaping/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/geoqiao/escaping/releases/tag/v0.1.0
12 changes: 8 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ A trusted presentation of the site's pages and content, selected independently f
_Avoid_: Publishing plugin, content source

**Theme API**:
The versioned agreement between the Site Compiler and a Theme: the `theme.yaml` manifest, the four template names (`site`, `page`, `theme`, `t`), the page templates the compiler renders and the `/assets/` URL space. Its version is distinct from the Site Compiler's release identity.
The versioned agreement between the Site Compiler and a Theme: the `theme.yaml` manifest, the four template names (`site`, `page`, `theme`, `t`), the template each page kind uses and its fallback, the `url` filter and the `/assets/` URL space. Its version is distinct from the Site Compiler's release identity.
_Avoid_: Visual style version, automatic compatibility adapter

**Built-in Theme**:
Expand All @@ -52,9 +52,13 @@ _Avoid_: Downloaded theme, compiler cache
A presentation choice declared with a type and default by a Theme's manifest and set by the site under `theme.options`. It has no meaning after switching to a Theme that does not declare it.
_Avoid_: Site setting, generator config

**Theme Page**:
An extra page declared by a Theme, either at one fixed route or once per Project Catalog Entry. Its route is registered with the same registry as compiler pages.
_Avoid_: Plugin page, site script
**Site Pages**:
The pages a site has, chosen in its Site Config under `pages`: each built-in section on, off or moved, plus extra pages at one fixed route or once per Project Catalog Entry, each rendered by a named Theme template. Routes are registered with the same registry as every other page.
_Avoid_: Theme page, plugin page

**Redirect**:
An old address of the site listed under `redirects` that sends visitors to a current page. A page always wins over a redirect.
_Avoid_: Alias, slug history

**Skipped Issue**:
Published Issue Content whose own content is invalid. It is left out of one build and reported by Issue number while the rest of the site is published.
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ Blog、Ideas、Projects、About、Tags 和 RSS,无需另建一套内容管理

使用 [escaping-template](https://github.com/geoqiao/escaping-template) 的 **Use this template** 创建站点,无需本机 Python、PAT 或手动创建发布标签。

> **公开预览:** 0.2.0 的 Action 和 starter workflow 已在本地测试中运行;在 GitHub 上从模板新建仓库的完整流程尚未验证。
> **公开预览:** 0.3.0 的 Action 和 starter workflow 已在本地测试中运行;在 GitHub 上从模板新建仓库的完整流程尚未验证。

1. 创建 `username.github.io`(免费账户使用公开仓库),保持 Issues/Actions 开启,在 **Settings → Pages** 把 **Source** 设为 **GitHub Actions**。
2. 保存一个带标题和 Markdown 正文的 Issue。workflow 的 `labels` 任务会在首次运行时创建发布标签;看不到时刷新 Issue 页面。
3. 添加一个 `type:blog`、`type:idea` 或 `type:about`,准备好后添加 `published`,在 Actions 中查看部署结果。

workflow 通过 `uses: geoqiao/escaping@v0.2.0` 调用生成器,站点仓库里不需要任何脚本。
普通 project Pages 子路径(如 `username.github.io/blog/`)不受支持;自定义域名须是 HTTPS 根 URL。
workflow 通过 `uses: geoqiao/escaping@v0.3.0` 调用生成器,站点仓库里不需要任何脚本。
仓库也可以不叫 `username.github.io`:这时网站在 `username.github.io/仓库名/` 下,escaping 会自动处理这个子路径。
详细操作、版本与失败恢复以[模板说明](starter/README.md)为准。

## 日常写作
Expand Down Expand Up @@ -93,8 +93,10 @@ theme:
从简单到完整,三种方式:

1. **改 Quiet 的选项**:在 `theme.options` 里设置,例如 `tagline`、`featured_posts`、`accent_color`。全部选项见 [Quiet](docs/themes/quiet.md)。
2. **只覆盖一个文件**:建一个 `theme/` 目录,放一个 `theme.yaml`(`api: 3` 和 `extends: quiet`)和你想替换的那个模板或静态文件,再设置 `theme: {use: ./theme}`。其余部分仍来自 Quiet。
3. **写一个完整 Theme**:见 [Theme 编写指南](docs/themes/authoring.md)。Theme 还可以声明自己的选项、界面文字和额外页面(例如每个项目一页)。
2. **只覆盖一个文件**:建一个 `theme/` 目录,放一个 `theme.yaml`(`api: 4` 和 `extends: quiet`)和你想替换的那个模板或静态文件,再设置 `theme: {use: ./theme}`。其余部分仍来自 Quiet。
3. **写一个自己的 Theme**:最少只要 `blog.html` 和 `post.html` 两个模板,见 [Theme 编写指南](docs/themes/authoring.md)。Theme 还可以声明自己的选项和界面文字。

网站有哪些页面、地址是什么,由你博客仓库里的 `config.yaml` 决定:`pages` 可以关掉或挪动某个页面、加上 `/now/` 这样的额外页面,`redirects` 让旧地址跳到新地址。见 [页面](docs/site-inputs.md#pages)。

改完后运行 `escpe theme check --config config.yaml`:它用示例内容离线渲染每个页面,不需要 Token,并报告 Theme 的问题。

Expand Down
12 changes: 7 additions & 5 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ Blog, Ideas, Projects, About, Tags and RSS, without a separate content managemen
Use **Use this template** at [escaping-template](https://github.com/geoqiao/escaping-template).
No local Python, PAT, or manually created publishing labels are required.

> **Public preview:** the 0.2.0 Action and starter workflow have run in local tests; creating a new
> **Public preview:** the 0.3.0 Action and starter workflow have run in local tests; creating a new
> repository from the template on GitHub has not been fully verified yet.

1. Create `username.github.io` (public for GitHub Free), keep Issues/Actions enabled, and in **Settings → Pages** set **Source** to **GitHub Actions**.
2. Save an Issue with a title and Markdown body. The workflow's `labels` job creates the publishing labels on its first run; refresh the Issue page if you do not see them.
3. Add one of `type:blog`, `type:idea`, or `type:about`, plus `published` when ready, and check the deployment in Actions.

The workflow runs the generator with `uses: geoqiao/escaping@v0.2.0`; the site repository needs no scripts.
Project-site sub-paths (such as `username.github.io/blog/`) are unsupported; a custom domain must serve an HTTPS root URL.
The workflow runs the generator with `uses: geoqiao/escaping@v0.3.0`; the site repository needs no scripts.
A repository with another name works too: the site then lives at `username.github.io/<repository>/`, and escaping handles that path.
The [starter instructions](starter/README.md) cover the full setup, versions and failure recovery.

## Everyday writing
Expand Down Expand Up @@ -90,8 +90,10 @@ Output and local Theme paths are relative to the Config directory. Serve the out
Three ways, from simplest to most complete:

1. **Change Quiet's options** under `theme.options`, such as `tagline`, `featured_posts` or `accent_color`. See [Quiet](docs/themes/quiet.md) for all of them.
2. **Override one file.** Create a `theme/` directory with a `theme.yaml` containing `api: 3` and `extends: quiet`, add only the template or static file you want to replace, and set `theme: {use: ./theme}`. Everything else still comes from Quiet.
3. **Write a complete Theme.** See the [Theme guide](docs/themes/authoring.md). A Theme can also declare its own options, interface text and extra pages (for example one page per project).
2. **Override one file.** Create a `theme/` directory with a `theme.yaml` containing `api: 4` and `extends: quiet`, add only the template or static file you want to replace, and set `theme: {use: ./theme}`. Everything else still comes from Quiet.
3. **Write your own Theme.** It needs only `blog.html` and `post.html`; see the [Theme guide](docs/themes/authoring.md). A Theme can also declare its own options and interface text.

Which pages the site has, and where, is up to your `config.yaml`: `pages` turns a page off, moves it or adds one such as `/now/`, and `redirects` keeps old addresses working. See [Pages](docs/site-inputs.md#pages).

Then run `escpe theme check --config config.yaml`. It renders every page with sample content, offline and without a token, and reports Theme problems.

Expand Down
3 changes: 1 addition & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ runs:
repository: $repo.full_name,
owner_login: $repo.owner.login,
owner_type: $repo.owner.type,
pages_base_url: $pages.html_url,
pages_base_path: "/"
pages_base_url: $pages.html_url
}' > "$RUNNER_TEMP/escaping-context.json"

- name: Build the site
Expand Down
Loading
Loading