docs: default visible columns contract (v1.0.79)
All checks were successful
技能自动化发布 / release (push) Successful in 5s

沉淀 _jiangchang_tables.default_visible_columns_json:技能声明默认展示列;未配置全显;用户改「列」后本机覆盖;与 visible=0 硬隐藏区分。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-14 10:17:17 +08:00
parent b27dd5b5ba
commit 49e4d6e9a6
7 changed files with 123 additions and 7 deletions

View File

@@ -9,6 +9,13 @@
- 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节 - 小节正文写用户能看懂的变化(新能力、修复、注意事项),避免堆砌内部实现细节
- 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段 - 打 tag 前**必须**为即将发布的版本新增一节;找不到匹配小节时,本次发布不会更新 changelog 字段
## 1.0.79
- 沉淀数据管理表级「默认展示列」契约:`_jiangchang_tables.default_visible_columns_json`
- 已配置则宿主默认只显示名单列;未配置全显;用户改过「列」后以本机偏好为准
- 与 `_jiangchang_columns.visible=0` 硬隐藏区分:默认隐藏仍可在「列」面板勾回
- `upsert_table_metadata` / 旧库幂等 `ALTER` 已支持该字段(见 `references/SCHEMA.md`)
## 1.0.78 ## 1.0.78
- 反哺 RPA 有界就绪等待约定:状态等待统一用 kit `poll_until`(需 `jiangchang-platform-kit>=1.2.11`) - 反哺 RPA 有界就绪等待约定:状态等待统一用 kit `poll_until`(需 `jiangchang-platform-kit>=1.2.11`)

View File

@@ -1,7 +1,7 @@
--- ---
name: 技能开发模板(通用业务版) name: 技能开发模板(通用业务版)
description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。" description: "OpenClaw 通用业务技能开发模板,供复制后定制新业务 skill。定制步骤见 development/DEVELOPMENT.md。"
version: 1.0.78 version: 1.0.79
author: 深圳匠厂科技有限公司 author: 深圳匠厂科技有限公司
metadata: metadata:
openclaw: openclaw:

View File

@@ -473,6 +473,7 @@ HINT: <next step>
- 臆造 `sortOrder` / `order` / `priority` 等字段指望宿主排序(模板 Schema `additionalProperties: false` 会拒收)。 - 臆造 `sortOrder` / `order` / `priority` 等字段指望宿主排序(模板 Schema `additionalProperties: false` 会拒收)。
- 依赖或宣传表顶栏「更多操作」溢出菜单(宿主已取消该能力)。 - 依赖或宣传表顶栏「更多操作」溢出菜单(宿主已取消该能力)。
- 把「表格行数据默认排序」与按钮顺序混淆——行排序见 [`SCHEMA.md`](SCHEMA.md) 的 `default_sort_json`。 - 把「表格行数据默认排序」与按钮顺序混淆——行排序见 [`SCHEMA.md`](SCHEMA.md) 的 `default_sort_json`。
- 把「默认展示列」与 `visible=0` 硬隐藏混淆——默认展示列见 [`SCHEMA.md`](SCHEMA.md) 的 `default_visible_columns_json`。
- 新增 `row-context-menu` 等 placement,或要求技能再维护一份与 `row` 平行的菜单清单。 - 新增 `row-context-menu` 等 placement,或要求技能再维护一份与 `row` 平行的菜单清单。
### Action 类型 × placements(常见示例,不是限制) ### Action 类型 × placements(常见示例,不是限制)

View File

@@ -139,6 +139,7 @@ metadata:
| `visible` | 是 | `1` 在宿主数据管理中展示 / `0` 隐藏 | | `visible` | 是 | `1` 在宿主数据管理中展示 / `0` 隐藏 |
| `readonly` | 是 | `1` 整张表只读;`0` 允许宿主根据字段权限执行写操作 | | `readonly` | 是 | `1` 整张表只读;`0` 允许宿主根据字段权限执行写操作 |
| `default_sort_json` | 否 | 数据管理**默认行排序**(见下节);空则宿主用内置默认 | | `default_sort_json` | 否 | 数据管理**默认行排序**(见下节);空则宿主用内置默认 |
| `default_visible_columns_json` | 否 | 数据管理**默认展示列**(见下节);空则全显 |
#### 表级默认排序 `default_sort_json`(宿主支持) #### 表级默认排序 `default_sort_json`(宿主支持)
@@ -171,12 +172,49 @@ upsert_table_metadata(
) )
``` ```
#### 表级默认展示列 `default_visible_columns_json`(宿主支持)
技能可声明该表在数据管理表格中的**默认展示列**(软默认,不是硬隐藏):
- **已配置**:首次打开(用户尚未改过「列」)只显示名单中的列;其余列仍在「列」面板中,用户可勾回
- **未配置 / 空 / 无效**:显示全部 schema 可见列(与今日行为一致)
- **用户改过「列」或表头「隐藏此列」后**:以本机偏好为准;「恢复默认列布局」后重新回到技能默认(若有)
JSON 数组(物理列名;也可写 `{"column":"col"}` 对象,宿主同样接受):
```json
["stat_type","period","stage","ops_count","timeout_ratio"]
```
规则:
- 列名必须是该表真实列,且 `_jiangchang_columns.visible=1`(硬隐藏列本来就不进表格)
- 与 `visible=0` **不同**:`visible=0` = 列从数据管理 schema /「列」面板移除;本字段只影响默认勾选状态
- 名单为空或全部无效时,宿主 fail-open:全显
- 通过 `upsert_table_metadata(..., default_visible_columns_json=...)` 写入;旧库由 `init_display_metadata_tables` 幂等 `ALTER` 补列
示例(时效统计:默认不展示编号与时间戳列):
```python
upsert_table_metadata(
cursor,
table_name="timeliness_stats",
display_name="时效统计",
readonly=1,
default_visible_columns_json=(
'["stat_type","period","stage","ops_count",'
'"timeout_open","timeout_done","timeout_total","timeout_ratio"]'
),
)
```
禁止: 禁止:
- 使用中文真实表名 - 使用中文真实表名
- 使用 `_schema_meta` 作为新的正式契约 - 使用 `_schema_meta` 作为新的正式契约
- 只写英文物理名而不写中文 `display_name` - 只写英文物理名而不写中文 `display_name`
- 让元数据引用不存在的表 - 让元数据引用不存在的表
- 用 `visible=0` 假装「默认不展示但用户还能勾出来」(那是硬隐藏,勾不回来)
### `_jiangchang_columns` 字段级标准 ### `_jiangchang_columns` 字段级标准
@@ -187,7 +225,7 @@ upsert_table_metadata(
| `display_name` | 是 | 用户可见的中文字段名(表头与表单标签);保持简短 | | `display_name` | 是 | 用户可见的中文字段名(表头与表单标签);保持简短 |
| `description` | 否 | 字段说明(给人看的短文案);**宿主在新增/编辑表单标签下方展示**(见下节);不要塞进 `display_name` | | `description` | 否 | 字段说明(给人看的短文案);**宿主在新增/编辑表单标签下方展示**(见下节);不要塞进 `display_name` |
| `display_order` | 否 | 兼容字段,默认保持 `NULL` | | `display_order` | 否 | 兼容字段,默认保持 `NULL` |
| `visible` | 是 | 是否在数据管理表格中展示 | | `visible` | 是 | 是否进入数据管理 schema /「列」面板(`0` = **硬隐藏**,用户无法勾回;默认不展示但可勾回请用表级 `default_visible_columns_json`) |
| `searchable` | 是 | 是否参与搜索 | | `searchable` | 是 | 是否参与搜索 |
| `editable` | 是 | `1` 业务字段允许宿主用于新增和编辑;`0` 系统/只读字段 | | `editable` | 是 | `1` 业务字段允许宿主用于新增和编辑;`0` 系统/只读字段 |
| `display_type` | 否 | 字段渲染类型;稳定取值见下节「表单与展示:稳定字段类型」 | | `display_type` | 否 | 字段渲染类型;稳定取值见下节「表单与展示:稳定字段类型」 |

View File

@@ -25,7 +25,8 @@ CREATE TABLE IF NOT EXISTS _jiangchang_tables (
sort_order INTEGER, sort_order INTEGER,
visible INTEGER NOT NULL DEFAULT 1, visible INTEGER NOT NULL DEFAULT 1,
readonly INTEGER NOT NULL DEFAULT 0, readonly INTEGER NOT NULL DEFAULT 0,
default_sort_json TEXT default_sort_json TEXT,
default_visible_columns_json TEXT
) )
""" """
@@ -89,6 +90,10 @@ def _ensure_jiangchang_tables_columns(cursor) -> None:
cols = {str(row[1]) for row in cursor.fetchall()} cols = {str(row[1]) for row in cursor.fetchall()}
if "default_sort_json" not in cols: if "default_sort_json" not in cols:
cursor.execute("ALTER TABLE _jiangchang_tables ADD COLUMN default_sort_json TEXT") cursor.execute("ALTER TABLE _jiangchang_tables ADD COLUMN default_sort_json TEXT")
if "default_visible_columns_json" not in cols:
cursor.execute(
"ALTER TABLE _jiangchang_tables ADD COLUMN default_visible_columns_json TEXT"
)
def init_display_metadata_tables(cursor) -> None: def init_display_metadata_tables(cursor) -> None:
@@ -106,27 +111,42 @@ def upsert_table_metadata(
visible: int = 1, visible: int = 1,
readonly: int = 0, readonly: int = 0,
default_sort_json: str | None = None, default_sort_json: str | None = None,
default_visible_columns_json: str | None = None,
) -> None: ) -> None:
"""写入表级元数据。 """写入表级元数据。
default_sort_json: 宿主数据管理默认排序,JSON 数组,例如 default_sort_json: 宿主数据管理默认排序,JSON 数组,例如
'[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]' '[{"column":"sent_at","direction":"desc"},{"column":"id","direction":"desc"}]'
为空则宿主回退到 updated_at → created_at → 主键。 为空则宿主回退到 updated_at → created_at → 主键。
default_visible_columns_json: 宿主数据管理默认展示列,JSON 字符串数组,例如
'["stat_type","period","stage"]'
为空则全显;用户改过「列」后以本机偏好为准(软默认,区别于 visible=0 硬隐藏)。
""" """
cursor.execute( cursor.execute(
""" """
INSERT INTO _jiangchang_tables ( INSERT INTO _jiangchang_tables (
table_name, display_name, description, visible, readonly, default_sort_json table_name, display_name, description, visible, readonly,
default_sort_json, default_visible_columns_json
) )
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(table_name) DO UPDATE SET ON CONFLICT(table_name) DO UPDATE SET
display_name = excluded.display_name, display_name = excluded.display_name,
description = excluded.description, description = excluded.description,
visible = excluded.visible, visible = excluded.visible,
readonly = excluded.readonly, readonly = excluded.readonly,
default_sort_json = excluded.default_sort_json default_sort_json = excluded.default_sort_json,
default_visible_columns_json = excluded.default_visible_columns_json
""", """,
(table_name, display_name, description, visible, readonly, default_sort_json), (
table_name,
display_name,
description,
visible,
readonly,
default_sort_json,
default_visible_columns_json,
),
) )

View File

@@ -507,6 +507,54 @@ metadata:
self.assertIn("seed_column_metadata_batch", text) self.assertIn("seed_column_metadata_batch", text)
self.assertIn("valid_column_names", text) self.assertIn("valid_column_names", text)
def test_default_visible_columns_json_upsert_and_migrate(self) -> None:
conn = sqlite3.connect(":memory:")
try:
from db.display_metadata import (
init_display_metadata_tables,
upsert_table_metadata,
)
cur = conn.cursor()
# Simulate pre-1.0.79 table without the new column.
cur.execute(
"""
CREATE TABLE _jiangchang_tables (
table_name TEXT PRIMARY KEY,
display_name TEXT NOT NULL,
description TEXT,
sort_order INTEGER,
visible INTEGER NOT NULL DEFAULT 1,
readonly INTEGER NOT NULL DEFAULT 0,
default_sort_json TEXT
)
"""
)
init_display_metadata_tables(cur)
cur.execute("PRAGMA table_info('_jiangchang_tables')")
cols = {str(row[1]) for row in cur.fetchall()}
self.assertIn("default_visible_columns_json", cols)
payload = '["stat_type","period","stage"]'
upsert_table_metadata(
cur,
table_name="timeliness_stats",
display_name="时效统计",
readonly=1,
default_visible_columns_json=payload,
)
conn.commit()
cur.execute(
"""
SELECT default_visible_columns_json
FROM _jiangchang_tables
WHERE table_name = 'timeliness_stats'
"""
)
self.assertEqual(cur.fetchone()[0], payload)
finally:
conn.close()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -344,6 +344,8 @@ class TestDocsStandards(unittest.TestCase):
"DEFAULT", "DEFAULT",
"description", "description",
"文件 / 图片上传", "文件 / 图片上传",
"default_visible_columns_json",
"默认展示列",
): ):
self.assertIn(marker, text, msg=f"SCHEMA.md missing {marker!r}") self.assertIn(marker, text, msg=f"SCHEMA.md missing {marker!r}")