@pactor-app/skills 1.43.0 → 1.45.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pactor-app/skills",
3
- "version": "1.43.0",
3
+ "version": "1.45.0",
4
4
  "description": "Agent skills for rapidly building pactor Server-Driven UI applications",
5
5
  "keywords": [
6
6
  "pactor",
@@ -48,6 +48,8 @@ Activate when the user works with **Pactor DSL or runtime APIs** in a project th
48
48
  | `packages/router` | `@pactor-app/router` | Dynamic routing, Path Matcher, menu registry |
49
49
  | `packages/permission` | `@pactor-app/permission` | Page/menu/component/action guards |
50
50
  | `packages/ui` | `@pactor-app/ui` | Built-in default UI implementation, shadcn style (components + prebuilt stylesheet + interaction core) |
51
+ | `packages/blocks` | `@pactor-app/blocks` | Composite business blocks (Login, CardList, EntityCard, RecordingPage, SiteFooter, …) with own stylesheet; NOT in the builtin registry — merge via `registerBlockComponents` |
52
+ | `packages/chart` | `@pactor-app/chart` | ECharts-based charts (LineChart, ColumnChart, PieChart, MetricCard, …); register via `registerChartComponents` |
51
53
  | `packages/markdown` | `@pactor-app/markdown` | Markdown + DSL Part rendering |
52
54
  | `packages/chat` | `@pactor-app/chat` | AI conversation: message protocol, streaming, ChatView, `Chat`/`ChatShell` DSL components, artifact panel |
53
55
  | `packages/vite-plugin` | `@pactor-app/vite-plugin` | Vite YAML modules, build-time validation, HMR |
@@ -381,7 +383,8 @@ pnpm pactor create my-app
381
383
  - **Do not** write `if (type === 'Button')` branches in a Renderer; use `ComponentRegistry`.
382
384
  - **Do not** inject JavaScript from the server into DSL; register actions/services/capabilities in the frontend.
383
385
  - **Do not** rely on frontend permission checks for security; enforce authorization on backend APIs.
384
- - **Do not** forget to import `@pactor-app/ui/styles.css` when using the default shadcn kit (`@pactor-app/chat/styles.css` additionally for chat surfaces).
386
+ - **Do not** expect `@pactor-app/blocks` / `@pactor-app/chart` components to be in the builtin registry; merge them explicitly (`registerBlockComponents` / `registerChartComponents` into the existing `ComponentRegistry`).
387
+ - **Do not** forget to import `@pactor-app/ui/styles.css` when using the default shadcn kit (`@pactor-app/chat/styles.css` / `@pactor-app/blocks/styles.css` / `@pactor-app/chart/styles.css` additionally for chat / blocks / chart surfaces).
385
388
  - **Do not** put a `type` key in action `params` (see the events workflow pitfall).
386
389
  - **Do not** place bare form controls directly under `Form`; wrap every control (including search-bar buttons) in a `Field`.
387
390
  - **Do not** expect DslNode slots (sidebar / toolbar / inspector / …) to render outside a page runtime; in pure React hosts pass ReactNode instead.
@@ -508,9 +508,9 @@ state:
508
508
  disabled: ${state.cityOptions.length === 0}
509
509
  ```
510
510
 
511
- ### 8.6 Card list page (CardList / EntityCard business components)
511
+ ### 8.6 Card list page (`CardList` / `EntityCard` — @pactor-app/blocks business components)
512
512
 
513
- `CardList` renders a grid of `EntityCard` summary cards in one node — no `For` + nested `Flex`/`Text`/`Tag` scaffolding. Item fields mirror `EntityCard` props; extra business fields ride along and come back in event payloads.
513
+ `CardList` renders a grid of `EntityCard` summary cards in one node — no `For` + nested `Flex`/`Text`/`Tag` scaffolding. Business components live in `@pactor-app/blocks` and are **not** in the builtin registry: merge them with `registerBlockComponents`. Item fields mirror `EntityCard` props; extra business fields ride along and come back in event payloads.
514
514
 
515
515
  ```yaml
516
516
  state:
@@ -34,7 +34,7 @@ version: 1.6.0
34
34
  6. **列表页操作列规范**:操作列按钮样式统一 `Button variant: link`;「编辑」与「新建」指向同一个表单(同一路由页或同一弹窗),唯一区别是编辑以行数据预填(路由页 `initialValues: ${data.detail}`,弹窗/内嵌面板 `form.setValues`);**数据列表页(搜索 + 表格 + 新建/编辑)以 `template/src/server/yaml/models/data-list.yaml` 为标准骨架生成**(结构与要点见 `references/page-templates.md`「数据列表标准模型」)
35
35
  7. **文案以线框图为准**:标题、按钮、空态文案逐字取自线框图/PRD
36
36
  8. **登录页用 `Login` 业务组件**:PRD 含登录页时,以模板 `yaml/common/login.yaml`(`type: Login`)为样板生成,禁止手写 Card+Form+自定义 CSS;**原型未明确布局方式时必须向用户提问**选择 `layout`(`split` 左右分栏 / `centered` 居中 / `brand-card` 品牌卡片 / `overlap` 卡片叠加 / `backdrop` 整页渐变背景,附效果简述,必要时含 `brandSide`/`brandRatio`/`authVariant` 修饰项),原型可判定布局则直接用、不提问;用户不答默认 `split` + `authVariant: card`;品牌区文案/logo/插画取自 PRD,提交闭环沿用 `onSubmit` → `request` + `message` + `app.reload` 模式
37
- 9. DSL 语法细节(组件 props、表达式、动作)以 `pactor-dsl` skill 与 packages/ui 当前导出为准,本 skill 只约定流程与产出物
37
+ 9. DSL 语法细节(组件 props、表达式、动作)以 `pactor-dsl` skill 与 packages 当前导出(ui 内置注册表 / `@pactor-app/blocks` / `@pactor-app/chart`)为准,本 skill 只约定流程与产出物
38
38
 
39
39
  ## 流程总览
40
40
 
@@ -73,7 +73,7 @@ version: 1.6.0
73
73
  - **增量**:仅 **add** 新建文件;**update** 改已有 yaml;**deprecate** 删文件并改 sitemap(须用户确认)
74
74
  - 校验见 `page-inventory-and-files.md`(全量)或 `incremental-wireframe-delta.md`(增量)
75
75
  - 按 `references/component-mapping.md` 映射组件;按 `references/page-templates.md` 选列表/详情/表单模板(**数据列表页直接复制 `yaml/models/data-list.yaml` 替换占位符**);表单一律 `Form` 包裹
76
- - **组件/模板/业务组件拿不准时先查资产库**(`references/asset-library.md`):组件详情含可套用的 DSL 示例与 whenToUse,线框模板含可渲染的提炼 DSL;业务组件以 details 条目 + packages/ui 导出为准(catalog 的 integration 字段不可信)
76
+ - **组件/模板/业务组件拿不准时先查资产库**(`references/asset-library.md`):组件详情含可套用的 DSL 示例与 whenToUse,线框模板含可渲染的提炼 DSL;业务组件以 details 条目 + `@pactor-app/blocks` 导出为准(catalog 的 integration 字段不可信)
77
77
  - 同步更新 `sitemap.yaml` 的 `pages` + `menus`(hidden 详情/编辑带 `activeMenu`)
78
78
  - 每页 dataSources 全部 `type: http`:**有接口文档 → url 取文档接口地址**;无 → 按 `references/data-api.md` 命名约定写 `/api/d/...`(转发层空数据回退,无需 mock);交互 events + actions 闭环
79
79
 
@@ -32,14 +32,14 @@
32
32
 
33
33
  **阶段 3 · 业务组件**
34
34
  - `components/business.ts` 有 26 个文档条目,但 **catalog 的 `integration` 字段不可信**(文档规范条目也标 integrated);
35
- - **只有 `details/business-details.ts` 中存在条目的 9 个**(Login / EntityCard / CardList / ExpressionEditor / FieldCondition / RuleConfig / RuleExpression / ScriptEditor / SearchableSelect)是 `packages/ui` 真实实现、可直接用;
35
+ - 业务组件已从 `packages/ui` 拆出为 **`@pactor-app/blocks`**(不进 ui 内置注册表,接入方用 `registerBlockComponents` 合并)。**真实导出的 10 个**(Login / EntityCard / CardList / RecordingPage / SiteFooter / ExpressionEditor / FieldCondition / RuleConfig / RuleExpression / ScriptEditor)可直接用;`SearchableSelect` 已转为内部实现(`internal/`),不对外导出;
36
36
  - 其余条目(高级表格、人员选择器等)只有产品文档定义、无实现——线框图命中时用基础组件组合近似,并在交付说明中标注差距。
37
37
 
38
38
  **阶段 4 · 样式**:主题家族与令牌定义在 `assets-data/themes/` / `tokens/`,与 `references/theme-and-style.md` 配合。
39
39
 
40
40
  ## 权威顺序与核对
41
41
 
42
- 1. `packages/ui` 当前导出(`src/components/`,含 `register.ts` 的 DSL 类型注册)——最终以代码为准;
42
+ 1. packages 当前导出(`packages/ui/src/components/register.ts` 内置注册表 + `@pactor-app/blocks` / `@pactor-app/chart` 导出)——最终以代码为准;
43
43
  2. `details/*-details.ts` 的 `dsl` 示例——已按真实实现核对过;
44
44
  3. `components/*.ts` catalog 描述——仅作选型参考。
45
45
 
@@ -1,6 +1,6 @@
1
1
  # 线框图区域 → pactor 组件映射表
2
2
 
3
- 按线框图的「区域」反查组件。只列稳定组件与常用 props 模式;**以 packages/ui 当前导出(`packages/ui/src/components/`)为准**,新增/改名时同步本表。
3
+ 按线框图的「区域」反查组件。只列稳定组件与常用 props 模式;**以 packages 当前导出为准**(内置组件见 `packages/ui/src/components/register.ts`,业务组件见 `@pactor-app/blocks`,图表见 `@pactor-app/chart`),新增/改名时同步本表。
4
4
 
5
5
  | 线框图区域 | pactor 组件 | 常用模式 |
6
6
  | --- | --- | --- |
@@ -13,12 +13,13 @@
13
13
  | 下拉筛选(操作人等枚举) | `Select` | `options` 可绑 `${data.xxx}`(接口下发选项,单段路径) |
14
14
  | 日期范围筛选 | `DatePicker` | range 模式绑 state,onChange reload |
15
15
  | 数据表格 | `Table` | `columns` + `dataSource: ${data.x.items}`;操作列按钮统一 `Button variant: link`;「编辑」与「新建」共用同一表单,仅以预填数据区分(initialValues / form.setValues) |
16
- | 卡片网格(约 3-4 列) | `CardList`(业务组件) | 数据项直接携带 `icon`/`title`/`subtitle`/`tags`/`description`/`meta`/`actions` 字段;`columns`、`createText`(首格虚线新建卡)、`showCount`、事件 `onItemClick`/`onItemAction`/`onCreateClick`(`${event}` 含 item/index/key);单卡即 `EntityCard`,无需 `For`+`Card` 手写三层结构 |
16
+ | 卡片网格(约 3-4 列) | `CardList`(`@pactor-app/blocks` 业务组件) | 数据项直接携带 `icon`/`title`/`subtitle`/`tags`/`description`/`meta`/`actions` 字段;`columns`、`createText`(首格虚线新建卡)、`showCount`、事件 `onItemClick`/`onItemAction`/`onCreateClick`(`${event}` 含 item/index/key);单卡即 `EntityCard`,无需 `For`+`Card` 手写三层结构 |
17
17
  | 状态徽标(启用/停用/告警) | `Badge` / `StatusBadge` | 颜色语义化,状态文案取自线框图 |
18
18
  | 启停开关图标(⏻) | `StatusToggleIcon` | 卡片/行内启停,onChange 闭环动作 + reload |
19
19
  | 权限控制显隐(无权限隐藏新建卡等) | 表达式 `hidden` / `if` 动作 | 绑 `${data.session.permissions}` 等,按会话裁剪 |
20
20
  | 统计数字卡 | `Statistic` + `Card` | 仪表盘顶部指标行,`Grid` 排布 |
21
- | 趋势/占比图 | `chart/` 下 AreaChart 等 | 图表随肤切换由库内建,无需额外处理 |
21
+ | 录音工作区(录音列表 + 转写详情 + 播放条) | `RecordingPage`(`@pactor-app/blocks`) | 数据化 props(`groups` / `tabs` / `segments` / `summary` / `insight` / `memory` / `translation` / `player`),事件 `onSelectRecording` / `onTabChange` / `onNewRecording` 等;常作 `ChatPage` 的 `content` 槽 |
22
+ | 趋势/占比图 | `@pactor-app/chart` 的 LineChart / PieChart 等 | 需 `registerChartComponents` 注册;图表随肤切换由库内建,无需额外处理 |
22
23
  | 描述列表(详情字段) | `Descriptions` | `items` 由 detail 接口下发或静态声明 |
23
24
  | 详情 Tab | `Tabs` | `items: [{key, label}]`;线框图为纵向分区时改用多个 `Card` 顺序排布 |
24
25
  | 表单(独立页 / 内嵌面板 / 弹窗抽屉) | `Form` 包裹 `Input`/`Select`/`NumberInput`/`RadioGroup`/`Checkbox`/`Textarea` 等 | 字段 `props.name` 绑定;校验用 `rules`;提交按钮 `submit: true` + `onSubmit`;回填联动用 `form.setValues` 动作。禁止散放字段 + setState 手写校验 |
@@ -1,6 +1,6 @@
1
1
  # 页面 YAML 模板
2
2
 
3
- 常用页面模板。占位符:`{{kebab-id}}` 页面 id、`{{页面标题}}`、`{{模块名}}`、`{{resource}}` 数据接口名(单段,见 data-api.md)、`{{实体名}}` 业务实体中文名。DSL 语法细节以 `pactor-dsl` skill 与 packages/ui 当前导出为准。
3
+ 常用页面模板。占位符:`{{kebab-id}}` 页面 id、`{{页面标题}}`、`{{模块名}}`、`{{resource}}` 数据接口名(单段,见 data-api.md)、`{{实体名}}` 业务实体中文名。DSL 语法细节以 `pactor-dsl` skill 与 packages 当前导出(ui 内置注册表 / `@pactor-app/blocks` / `@pactor-app/chart`)为准。
4
4
 
5
5
  **数据列表页(搜索 + 表格 + 新建/编辑)默认套用「数据列表标准模型」**;仅当新建/编辑是独立路由表单页时才用轻量变体。
6
6
 
@@ -338,7 +338,7 @@ body:
338
338
  - **条件显隐**:`onValuesChange` 把字段值镜像到 state(`"${event.x != null ? event.x : state.x}"` 保持其他字段变化时原值),再用 `when` 控制显隐;编辑时关键字段 `disabled: ${state.editingId != ''}`
339
339
  - 空 `editingId` 时后端 detail 接口须返回空对象而非 404(转发层本身对 404 也回退空对象,但真实后端仍建议遵守该约定)
340
340
 
341
- ## 登录页(Login 业务组件)
341
+ ## 登录页(Login 业务组件,`@pactor-app/blocks`)
342
342
 
343
343
  登录页一律用 DSL `type: Login` 业务组件,禁止手写 Card+Form+自定义 CSS。**样板即模板工程的 `template/src/server/yaml/common/login.yaml`**(可直接运行预览),核心结构:
344
344
 
@@ -382,7 +382,7 @@ body:
382
382
 
383
383
  - **布局是产品决策**:原型未明确布局时必须向用户提问(5 种 `layout` + `brandSide`/`brandRatio`/`authVariant` 等修饰),用户不答默认 `split` + `card`;原型可判定则不提问
384
384
  - 页面布局(sitemap `layouts`)用纯 PageOutlet 外壳(模板为 `login` 布局),组件自带整页渐变、背景装饰与品牌面板,不要再套自定义背景容器
385
- - 可配置项权威定义见 `openspec/specs/ui-login-component` 与组件源码(`packages/ui/src/components/business/Login/`);未配置的可选元素不渲染
385
+ - 可配置项权威定义见 `openspec/specs/ui-login-component` 与组件源码(`packages/blocks/src/components/Login/`);未配置的可选元素不渲染
386
386
  - 校验失败由组件内部 Form rules 就地提示;登录失败/成功分支在 DSL 动作链用 `if` + `message` 实现
387
387
 
388
388
  ## 列表页(轻量变体:新建/编辑跳独立表单页)
@@ -3,10 +3,12 @@ import { Geist, Geist_Mono } from "next/font/google";
3
3
  import { PactorApp } from "@/pactor/App";
4
4
  // 顺序约定:globals(本项目 tailwind 工具类 + 皮肤无关的外壳规则)→
5
5
  // ui 预建样式(同层冲突时 ui 胜出,保证其内部 base/响应式变体排序生效,
6
- // 如 Sidebar 的 hidden md:block)。主题令牌由 public/skins/<skin>.css
6
+ // 如 Sidebar 的 hidden md:block)→ blocks 业务组件样式(Login 等,独立
7
+ // 编译产物)。主题令牌由 public/skins/<skin>.css
7
8
  // 经下方内联脚本在首屏注入(link 后加载者胜出)。
8
9
  import "./globals.css";
9
10
  import "@pactor-app/ui/styles.css";
11
+ import "@pactor-app/blocks/styles.css";
10
12
 
11
13
  export const metadata: Metadata = {
12
14
  title: "Pactor Console",
@@ -11,6 +11,7 @@
11
11
  "build:test": "node scripts/build.mjs test"
12
12
  },
13
13
  "dependencies": {
14
+ "@pactor-app/blocks": "latest",
14
15
  "@pactor-app/core": "latest",
15
16
  "@pactor-app/runtime": "latest",
16
17
  "@pactor-app/ui": "latest",
@@ -3,6 +3,7 @@
3
3
  import { usePathname, useRouter, useSearchParams } from "next/navigation";
4
4
  import { Suspense } from "react";
5
5
  import { SitemapApp } from "pactor-app";
6
+ import { Login } from "@pactor-app/blocks";
6
7
 
7
8
  import { apiBaseUrl, pageEndpoint, sitemapSources } from "./config";
8
9
  import { pactorActions } from "./actions";
@@ -19,6 +20,10 @@ import { pactorComponents } from "./components";
19
20
  *(app/api/d/[...parts]/route.ts)原样转发到 serverBaseUrl(见 config.ts);
20
21
  * 没有服务层接口时转发层返回空数据,页面按空态渲染。
21
22
  *
23
+ * 业务区块组件(@pactor-app/blocks)不进 ui 内置注册表:login 页 DSL 的
24
+ * `type: Login` 经 `components` prop 注册(样式见 layout.tsx 的
25
+ * blocks/styles.css)。
26
+ *
22
27
  * 登录 / 登出是普通 HTTP 调用,由 DSL 中的内置 request 动作直接表达
23
28
  *(见 src/server/yaml/ 的登录页 / 布局 DSL),无需客户端注册服务。
24
29
  *
@@ -48,7 +53,7 @@ function PactorAppInner() {
48
53
  pathname={pathname}
49
54
  query={Object.fromEntries(searchParams.entries())}
50
55
  navigate={(to) => router.push(to)}
51
- components={pactorComponents}
56
+ components={{ ...pactorComponents, Login }}
52
57
  actions={pactorActions}
53
58
  pageEndpoint={pageEndpoint}
54
59
  />
@@ -1,4 +1,4 @@
1
- import { readFileSync, readdirSync } from "node:fs";
1
+ import { readFileSync, readdirSync, watch } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
 
4
4
  import type { PageDsl, PermissionConfig } from "@pactor-app/core";
@@ -20,7 +20,7 @@ import type { DemoUser } from "./accounts";
20
20
  * 动态字段(updatedAt / user / permissions)。
21
21
  */
22
22
 
23
- /* ---------------- YAML DSL 文件加载(src/server/yaml/,启动时读入内存) ---------------- */
23
+ /* ---------------- YAML DSL 文件加载(src/server/yaml/,缓存 + dev 下 fs.watch 失效重读) ---------------- */
24
24
 
25
25
  const YAML_DIR = join(process.cwd(), "src/server/yaml");
26
26
 
@@ -60,8 +60,6 @@ interface SitemapFile {
60
60
  menus: SitemapResource["menus"];
61
61
  }
62
62
 
63
- const site = loadYaml<SitemapFile>("sitemap.yaml");
64
-
65
63
  /** 递归收集 yaml 下所有页面 DSL(含 route / provider / ops 等域子目录;跳过 sitemap.yaml),按 id → 相对路径建索引 */
66
64
  function collectPageFiles(dir: string): Map<string, string> {
67
65
  return new Map(
@@ -75,21 +73,59 @@ function collectPageFiles(dir: string): Map<string, string> {
75
73
  );
76
74
  }
77
75
 
78
- const pageFiles = collectPageFiles(YAML_DIR);
76
+ interface YamlState {
77
+ site: SitemapFile;
78
+ pageDsls: Map<string, PageDsl>;
79
+ lazyPageDsls: Map<string, PageDsl>;
80
+ }
81
+
82
+ function loadState(): YamlState {
83
+ const site = loadYaml<SitemapFile>("sitemap.yaml");
84
+ const pageFiles = collectPageFiles(YAML_DIR);
85
+
86
+ function loadPageDsl(id: string): PageDsl {
87
+ const file = pageFiles.get(id);
88
+ if (!file) throw new Error(`页面 DSL 缺失:${id}.yaml`);
89
+ return loadYaml<PageDsl>(file);
90
+ }
91
+
92
+ // 页面 DSL:按 id 约定从 yaml/<域>/<id>.yaml 加载;lazy 页不内联进 sitemap
93
+ return {
94
+ site,
95
+ pageDsls: new Map(
96
+ site.pages.filter((p) => !p.lazy).map((p) => [p.id, loadPageDsl(p.id)]),
97
+ ),
98
+ lazyPageDsls: new Map(
99
+ site.pages.filter((p) => p.lazy).map((p) => [p.id, loadPageDsl(p.id)]),
100
+ ),
101
+ };
102
+ }
103
+
104
+ let stateCache: YamlState | null = null;
79
105
 
80
- function loadPageDsl(id: string): PageDsl {
81
- const file = pageFiles.get(id);
82
- if (!file) throw new Error(`页面 DSL 缺失:${id}.yaml`);
83
- return loadYaml<PageDsl>(file);
106
+ /** YAML 状态:首次请求时读入缓存;dev 下 yaml 目录任一文件变更由 fs.watch 失效缓存,下次请求重读,无需重启 */
107
+ function state(): YamlState {
108
+ if (!stateCache) stateCache = loadState();
109
+ return stateCache;
84
110
  }
85
111
 
86
- /** 页面 DSL:按 id 约定从 yaml/<域>/<id>.yaml 加载;lazy 页不在此内联 */
87
- const pageDsls = new Map(
88
- site.pages.filter((p) => !p.lazy).map((p) => [p.id, loadPageDsl(p.id)]),
89
- );
90
- const lazyPageDsls = new Map(
91
- site.pages.filter((p) => p.lazy).map((p) => [p.id, loadPageDsl(p.id)]),
92
- );
112
+ // watcher 挂 globalThis:Next dev 重编译会重新执行本模块,只更新失效回调、不重复建 watcher
113
+ const globalForYamlWatch = globalThis as unknown as {
114
+ __pactorYamlInvalidate?: () => void;
115
+ __pactorYamlWatcher?: ReturnType<typeof watch>;
116
+ };
117
+ globalForYamlWatch.__pactorYamlInvalidate = () => {
118
+ stateCache = null;
119
+ };
120
+ if (process.env.NODE_ENV !== "production" && !globalForYamlWatch.__pactorYamlWatcher) {
121
+ globalForYamlWatch.__pactorYamlWatcher = watch(
122
+ YAML_DIR,
123
+ { recursive: true },
124
+ () => {
125
+ globalForYamlWatch.__pactorYamlInvalidate?.();
126
+ },
127
+ );
128
+ }
93
129
 
94
130
  /* ---------------- 会话裁剪 ---------------- */
95
131
 
@@ -140,6 +176,7 @@ function filterMenus(
140
176
  * 匿名 → 仅 public 页;guest → + 首页;admin → + 用户列表 / 报表组。
141
177
  */
142
178
  export function buildSitemap(user: DemoUser | null): SitemapResource {
179
+ const { site, pageDsls } = state();
143
180
  const pages: SitemapResource["pages"] = site.pages
144
181
  .filter((p) => canSee(p, user))
145
182
  .map((p) => ({
@@ -167,6 +204,7 @@ export function getLazyPage(
167
204
  pageId: string,
168
205
  user: DemoUser | null,
169
206
  ): { version: number; pageDsl: PageDsl } | null {
207
+ const { site, lazyPageDsls } = state();
170
208
  const entry = site.pages.find((p) => p.id === pageId);
171
209
  const pageDsl = lazyPageDsls.get(pageId);
172
210
  if (!entry?.lazy || !pageDsl || !canSee(entry, user)) {
@@ -1,4 +1,5 @@
1
- # 登录页 DSL:type: Login 业务组件(packages/ui components/business/Login/),
1
+ # 登录页 DSL:type: Login 业务组件(@pactor-app/blocks,经 App.tsx 的
2
+ # components prop 注册;样式在 app/layout.tsx 挂载 blocks/styles.css),
2
3
  # 布局(layout: split / centered / brand-card / overlap / backdrop)与修饰
3
4
  # (brandSide / brandRatio / authVariant / brandPanelBackground / decor)正交组合,
4
5
  # 品牌区/文案/辅助入口/页脚全部 props 化,样式走语义令牌(深浅色/换肤自适应),