@sendevia/pi-ask-author 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # pi-ask-author
2
2
 
3
- [![pi-package](https://img.shields.io/badge/pi--package-extension-blue.svg)](https://pi.dev/packages)
4
- [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg)](https://www.typescriptlang.org/)
5
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ ![NPM Version](https://img.shields.io/npm/v/%40sendevia%2Fpi-ask-author?style=flat-square&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40sendevia%2Fpi-ask-author)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
6
6
 
7
7
  专为作家、轻小说创作者与 AI 协同写作设计的 [Pi](https://github.com/earendil-works/pi-coding-agent) 终端交互式请示扩展。
8
8
 
@@ -15,7 +15,7 @@
15
15
  - 🖥️ **双栏响应式 TUI 交互**
16
16
  - **左栏选项决策**:直观展示单选/多选状态、完成度指示徽标、选项标签与专属批注标识。
17
17
  - **右栏实时预览**:光标移动时即时联动渲染当前选项的**剧情因果推导**与 **Markdown 正文草稿预览**。
18
- - **自适应视口**:终端宽度不小于 72 列时启用双栏,窄屏下改为单栏内联展开与折叠。
18
+ - **自适应视口**:终端宽度不足时启用双栏,窄屏下改为单栏内联展开与折叠。
19
19
  - 📖 **小说级富文本与草稿高亮**
20
20
  - 内置紧凑轻量的终端 Markdown 渲染与小说语法高亮,完整支持 bold、italic、删除线、行内代码、引用区块。
21
21
  - 对中文小说台词(引号内对话高亮)与分段排版做了专门处理,作者可以在终端内直接检视行文节奏与语感。
@@ -26,11 +26,11 @@
26
26
  - **单题平铺模式**:快速发起单点决策(如重要剧情分歧、结局走向)。
27
27
  - **多题批量问卷模式**:支持在单次弹窗内汇集多个关联决策(如脱离路线 + 战利品搜刮 + 下章基调),配有顶部 Tab 标签栏与完成度总览页(Summary Tab),作者在一处完成全部确认。
28
28
  - 🎯 **确定性 LLM 封套与指令蒸馏 (Deterministic Envelopes)**
29
- - 工具返回给 LLM 的文本采用固定的结构化 Markdown 封套(`[Author Decision Finalized]` / `[Author Decision Cancelled]` / `[Author Consultation Error]`),前缀稳定,便于 Prompt Cache 命中。
29
+ - 工具返回给 LLM 的文本采用固定的结构化 Markdown 封套(`[Author Decision Finalized]` / `[Author Decision Cancelled]` / `[Author Consultation Error]`),前缀稳定,便于缓存命中。
30
30
  - **草稿指令提取 (Draft Directives)**:选中的选项草稿预览会自动提炼为紧凑的单行 `[Draft directive: ...]` 指令,直接供 LLM 在后续正文执笔中原样复用或参考。
31
31
  - 尾部附带执行指令,要求模型不再寒暄,直接进入下一步正文写作。
32
32
  - ⚡ **高性能与极简依赖**
33
- - 严格遵守 Pi 官方扩展生态规范,核心依赖 `@earendil-works/pi-coding-agent`、`@earendil-works/pi-tui` 与 `typebox` 均列为 `peerDependencies`,不引入其他第三方依赖。
33
+ - 严格遵守 Pi 官方扩展生态规范,不引入其他第三方依赖。
34
34
  - 资源回收:生命周期严格绑定 AbortSignal,定时器与事件监听幂等清理,渲染缓存按尺寸与状态键失效。
35
35
 
36
36
  ---
@@ -85,114 +85,6 @@ pi -e ./path/to/pi-ask-author
85
85
 
86
86
  ---
87
87
 
88
- ## 🤖 智能体提示词与调用规范 (Agent Guidelines)
89
-
90
- 在系统提示词或 Skill 中声明以下规则,可以引导 LLM 按预期调用该工具:
91
-
92
- ```markdown
93
- Whenever plot branches, character decisions, scene atmosphere, or outline directions need confirmation:
94
-
95
- - Call `ask_author` directly without conversational preambles.
96
- - Always provide a concise `formTitle`.
97
- - Use the `questions` array to batch interrelated decisions into ONE invocation; avoid consecutive single calls.
98
- - Include causal plot deductions in `description` and scene draft excerpts in `preview`.
99
- - Omit optional default fields (`id`, `description`, `minSelect`, `maxSelect`, `allowEmpty`, `allowCustom`) to maximize token economy and prompt caching.
100
- ```
101
-
102
- ### 参数示例:多题批量问卷
103
-
104
- ```json
105
- {
106
- "formTitle": "第七幕逃生抉择与伏笔分配",
107
- "questions": [
108
- {
109
- "title": "第一幕脱离路线选择",
110
- "options": [
111
- {
112
- "label": "炸毁地下蒸汽管网断后",
113
- "description": "引爆管网阻断追兵,代价是彻底暴露行踪并波及城区管线。",
114
- "preview": "“听着,艾略特,”雷恩咬紧牙关按下阀门,“我数到三,你往排污井跳!”\n蒸汽在接缝处发出濒临极限的尖啸,暗红警示灯将二人面孔映得如同凝血。"
115
- },
116
- { "label": "遁入旧排水暗渠迂回脱离", "description": "周旋于暗渠迷宫,隐匿行踪但有遭遇感染体风险。" }
117
- ]
118
- },
119
- {
120
- "title": "主角搜刮战利品与剧情伏笔(多选)",
121
- "multiSelect": true,
122
- "options": [
123
- { "label": "过载超频模块", "description": "战斗中可触发高热熔毁,但易过载自伤。" },
124
- { "label": "加密军方识别铭牌", "description": "揭示军方秘密人体实验编号,引发信任危机。" }
125
- ]
126
- }
127
- ]
128
- }
129
- ```
130
-
131
- ### 返回封套示例
132
-
133
- ```text
134
- [Author Decision Finalized]
135
- 1. [第一幕脱离路线选择]: 【炸毁地下蒸汽管网断后】引爆管网阻断追兵,代价是彻底暴露行踪并波及城区管线。 [Author note: 注意描写气压表的指针爆表碎裂细节] [Draft directive: '听着,艾略特,'雷恩咬紧牙关按下阀门,'我数到三,你往排污井跳!' / 蒸汽在接缝处发出濒临极限的尖啸,暗红警示灯将二人面孔映得如同凝血。]
136
- 2. [主角搜刮战利品与剧情伏笔(多选)]: 【过载超频模块】战斗中可触发高热熔毁,但易过载自伤。; 【加密军方识别铭牌】揭示军方秘密人体实验编号,引发信任危机。
137
- Execute workflow and writing strictly according to the author decisions and draft directives above without conversational acknowledgments.
138
- ```
139
-
140
- ---
141
-
142
- ## 🏗️ 架构与模块设计 (Architecture)
143
-
144
- 插件采用严格的单向无环依赖图分层构建,保证各个组件职责清晰、高内聚且易于测试:
145
-
146
- ```mermaid
147
- flowchart LR
148
- theme --> texts
149
- theme --> novel-markdown
150
- theme --> view-rows
151
- theme --> view-summary
152
- theme --> component
153
- texts --> format
154
- texts --> schema
155
- texts --> model
156
- texts --> novel-markdown
157
- texts --> view-rows
158
- texts --> view-summary
159
- texts --> component
160
- texts --> index
161
- format --> sanitize
162
- format --> model
163
- format --> novel-markdown
164
- format --> view-rows
165
- format --> view-summary
166
- format --> component
167
- format --> index
168
- schema --> sanitize
169
- schema --> model
170
- schema --> index
171
- model --> novel-markdown
172
- model --> view-rows
173
- model --> view-summary
174
- model --> component
175
- model --> index
176
- sanitize --> index
177
- novel-markdown --> component
178
- view-rows --> component
179
- view-summary --> component
180
- component --> index
181
- ```
182
-
183
- 箭头由被依赖模块指向依赖它的模块(`A --> B` 表示 `B` 导入 `A`)。
184
-
185
- - **`index.ts`**:扩展主入口,注册 `ask_author` 工具与 `/ask-author` 命令,负责结果工厂构造与卡片记忆化渲染。
186
- - **`component.ts`**:TUI 状态机核心组件,自行维护按键派生、光标记忆、视口滑动窗口与双栏排版。
187
- - **`model.ts`**:问卷表单数据模型,负责规范化题目前期推导、选项校验与答案序列化。
188
- - **`schema.ts`**:基于 TypeBox 构建的静态类型与运行时 Schema 单一数据源。
189
- - **`texts.ts`**:常量、终端图标、小说语法表与面向作者/LLM 双向文案的集中字典。
190
- - **`novel-markdown.ts`**:小说语法终端高亮层,在原生 Markdown 引擎解析前注入语义标记。
191
- - **`sanitize.ts`**:入参防御性清洗与别名兼容。
192
- - **`view-rows.ts` / `view-summary.ts`**:列表行与总览页的排版生成器。
193
-
194
- ---
195
-
196
88
  ## 🧪 开发与质量验证 (Development)
197
89
 
198
90
  本项目开启 TypeScript 严格模式:
@@ -0,0 +1,322 @@
1
+ /**
2
+ * @file component.ts
3
+ * @description `ask_author` 交互式 TUI 主组件(双栏问卷视图与答卷总览)
4
+ *
5
+ * 核心架构特性:
6
+ * - 宽屏双栏 / 窄屏单栏自适应:`renderWidth >= LAYOUT_CONFIG.splitViewMinWidth` 且题目含富内容时走双栏,否则降级单栏紧凑列表;预览与总览视口行数随终端高度收缩
7
+ * - 多级缓存失效:整屏(尺寸键 + 状态突变清空)、总览 / Tab 栏 / 左栏 / 菜单项(`revision` 键)、预览池(内容键);视口切片 O(1);`render()` 无 I/O,Markdown 渲染仅发生在派生缓存未命中时
8
+ * - 逐题独立光标记忆与 `1~9` 跳转题目;内置编辑器透传 Focusable 以定位 IME 候选框
9
+ * - Esc / Ctrl+C 双通道取消(输入模式二次确认);定时器、Abort 监听与缓存池全量回收
10
+ *
11
+ * 渲染纯度不变量:`render()` 及其可达被调函数禁止写入语义状态(`currentTab`、光标与各 `*ScrollOffset`、`inputMode`、`editTarget`、`editorEscArmed`、`statusMessage`、`isFinished`);滚动位移归一化一律归属输入路径
12
+ * 可写入透明备忘:`cached*`、`previewCache`、`tabCache`、`leftColCache`、`summaryCachedLines`、`menuCache`、`previewViewportObservation` 均为「渲染输入的纯函数」缓存
13
+ *
14
+ * 依赖方向:component.ts → theme / texts / format / model / novel-markdown / view-rows / view-summary;本组件只做按键路由、视口切片与整屏缓存编排
15
+ */
16
+ import { type Component, type Focusable, type TUI } from "@earendil-works/pi-tui";
17
+ import { AskAuthorFormModel, type AskAuthorResult } from "./model.js";
18
+ import type { Theme } from "./theme.js";
19
+ /**
20
+ * `ask_author` 交互式 TUI 组件(问卷作答与答卷总览)
21
+ * 状态机:作答突变经 `AskAuthorFormModel`,`revision` 驱动两级缓存失效;`render()` 只写入透明备忘 / 观察缓存,绝不写回滚动位移等语义状态;`dispose()` 幂等且只设置 `disposed`,`finish()` 以 `isFinished` 保证 `done` 至多投递一次
22
+ */
23
+ export declare class AskAuthorComponent implements Component, Focusable {
24
+ /** Focusable 接口要求:输入模式焦点指示 */
25
+ private _focused;
26
+ /** 当前焦点状态(仅 `true` 时透传内置 Editor 以定位 IME 候选框) */
27
+ get focused(): boolean;
28
+ /**
29
+ * 设置焦点状态并级联透传至内置 Editor(同值为无操作)
30
+ * @param value - 目标焦点状态;同时失效整屏 / Tab 栏 / 左栏缓存
31
+ */
32
+ set focused(value: boolean);
33
+ /** TUI 上下文 */
34
+ private readonly tui;
35
+ /** 主题 */
36
+ private readonly theme;
37
+ /** 问卷模型(作答状态唯一权威,mutation 自动推进 revision) */
38
+ private readonly model;
39
+ /** 结果达成回调 */
40
+ private readonly done;
41
+ /** 当前 Tab 索引(0-based;questionCount 为总览确认页) */
42
+ private currentTab;
43
+ /** 每道题独立的光标索引记忆表 */
44
+ private readonly cursorByTab;
45
+ /** 当前题目菜单内选中的条目索引 */
46
+ private currentOptionIndex;
47
+ /** 左侧菜单列表滚动位移(行) */
48
+ private listScrollOffset;
49
+ /** 详情预览区滚动位移(行) */
50
+ private previewScrollOffset;
51
+ /** 答卷总览页滚动位移(行) */
52
+ private summaryScrollOffset;
53
+ /** 是否处于补充说明文本输入模式 */
54
+ private inputMode;
55
+ /** Esc 丢弃确认武装标记:编辑器非空时首次 Esc 仅武装,再次 Esc 才丢弃内容 */
56
+ private editorEscArmed;
57
+ /** 底部临时状态通知文案 */
58
+ private statusMessage;
59
+ /** 状态通知定时器 */
60
+ private statusTimer;
61
+ /** 是否已终结(终结后忽略一切输入,且 `done` 不再投递) */
62
+ private isFinished;
63
+ /** 是否已卸载回收(幂等标记;卸载后忽略迟到输入且拒绝再绑定 Abort 监听;`done` 投递不受本标记阻断,见 {@link dispose}) */
64
+ private disposed;
65
+ /** AbortSignal 监听绑定对象 */
66
+ private abortBinding?;
67
+ /** 整屏缓存键:调用方真实列宽(输出截断依据) */
68
+ private cachedWidth?;
69
+ /** 整屏缓存键:布局列宽 `max(20, width)`(折行依据,20 列为窄终端宽度下限) */
70
+ private cachedLayoutWidth?;
71
+ /** 整屏缓存键:终端行数(视口行数随之自适应) */
72
+ private cachedHeight?;
73
+ /** 整屏已格式化终端行缓存 */
74
+ private cachedLines?;
75
+ /** 预览框完整渲染行缓存池(键:题号+条目身份+列宽+补充说明;超 {@link PREVIEW_CACHE_CAPACITY} 按插入序淘汰) */
76
+ private readonly previewCache;
77
+ /** 总览页未切片内容行缓存(键:列宽 + `revision`) */
78
+ private summaryCachedLines?;
79
+ /** Tab 栏行缓存(键:当前页 + `revision` + 列宽) */
80
+ private tabCache?;
81
+ /** 左栏菜单行缓存(键:题号 + 光标 + 位移 + `revision` + 左栏宽) */
82
+ private leftColCache?;
83
+ /** 菜单项派生缓存(键:题号 + `revision`,消除 render/输入路径重复构建) */
84
+ private menuCache?;
85
+ /** 上一帧预览视口的观察元数据(渲染写入、输入路径读取以归一化位移) */
86
+ private previewViewportObservation?;
87
+ /** 当前编辑器针对的目标(题目整体或指定选项) */
88
+ private editTarget?;
89
+ /** 内置补充说明编辑器实例 */
90
+ private readonly editor;
91
+ /**
92
+ * 构造交互式 TUI 主组件
93
+ * @param tui - TUI 上下文
94
+ * @param theme - 主题
95
+ * @param model - 问卷模型
96
+ * @param done - 结果回调
97
+ */
98
+ constructor(tui: TUI, theme: Theme, model: AskAuthorFormModel, done: (result: AskAuthorResult) => void);
99
+ /**
100
+ * 绑定中止信号(幂等,至多绑定一次)
101
+ * @param signal - 外层 AbortSignal;缺省 / 已绑定 / 已卸载时直接返回
102
+ */
103
+ bindAbortSignal(signal?: AbortSignal): void;
104
+ /**
105
+ * 清理定时器、Abort 监听与全部缓存池(幂等)
106
+ * 只设置 `disposed`、不设置 `isFinished`(资源回收 ≠ `done` 已投递),否则宿主先卸载而 `done` 未投递时挂起
107
+ */
108
+ dispose(): void;
109
+ /** 清空整屏帧缓存(列宽 / 行数键;不触发重绘) */
110
+ private clearFrameCaches;
111
+ /** 清空全部派生缓存(`revision` 键与内容键:预览池 / 总览 / Tab 栏 / 左栏 / 菜单项;不触发重绘) */
112
+ private clearDerivedCaches;
113
+ /** 状态突变时失效整屏缓存并请求重绘(保留派生缓存池复用) */
114
+ private touch;
115
+ /** 作答变更后的统一失效入口:模型已推进 revision,此处清空全部派生缓存 */
116
+ private touchDerived;
117
+ /**
118
+ * Component 接口约定:宿主重绘上下文(主题切换 / 需从头重绘)时清空全部渲染缓存
119
+ * 终端尺寸变化不调用本方法(宿主 resize 仅 `requestRender`),由整屏缓存键绑定列宽与行数自然失效
120
+ */
121
+ invalidate(): void;
122
+ /** 清理临时状态定时器(清空引用以便后续 setStatus 重新计时) */
123
+ private clearStatusTimer;
124
+ /**
125
+ * 触发底部临时状态通知
126
+ * @param msg - 提示文案(取自 `TEXTS.status`,不做控制字符清洗)
127
+ */
128
+ private setStatus;
129
+ /**
130
+ * 终结作答流程并触发 `done`(至多投递一次)
131
+ * `isFinished` 单守卫防止重复投递;不以 `disposed` 阻断(资源已回收但 `done` 未投递时仍须结算),跳转入 {@link dispose}
132
+ * @param cancelled - 是否用户主动取消
133
+ */
134
+ finish(cancelled: boolean): void;
135
+ /** Tab 总页数(题目页 + 总览提交页) */
136
+ private get totalTabs();
137
+ /** 当前 Tab 是否为答卷总览提交页 */
138
+ private get isSubmitTab();
139
+ /** 终端可视行数(`tui.terminal.rows` 不可用或非正数时兜底 24) */
140
+ private get terminalRows();
141
+ /** 总览页视口行数上限(随终端高度自适应;区间 `[4, LAYOUT_CONFIG.summaryPageSize]`) */
142
+ private get summaryPageSize();
143
+ /** 双栏右侧预览视口行数上限(区间 `[6, LAYOUT_CONFIG.splitPreviewHeight]`) */
144
+ private get splitPreviewHeight();
145
+ /** 单栏内联预览视口行数上限(区间 `[3, LAYOUT_CONFIG.singlePreviewHeight]`) */
146
+ private get singlePreviewHeight();
147
+ /**
148
+ * 当前题目的菜单项
149
+ * @returns 菜单项数组;按 `(题号, revision)` 记忆化,命中时零分配复用
150
+ */
151
+ private getMenuItems;
152
+ /**
153
+ * 当前聚焦菜单项
154
+ * @returns 越界安全查询结果;无菜单项或索引越界时为 undefined
155
+ */
156
+ private currentMenuItem;
157
+ /**
158
+ * 切换题目 / 总览页 Tab,重置滚动位移并按 `cursorByTab` 恢复光标记忆
159
+ * @param newTab - 目标 Tab 索引(0-based;questionCount 为总览页;同页为无操作)
160
+ */
161
+ private switchTab;
162
+ /** 收敛光标至合法区间并同步左栏滚动窗口,保证聚焦项可见(菜单项为空时直接返回) */
163
+ private adjustScrollForCurrentOption;
164
+ /**
165
+ * 移动菜单光标并同步滚动窗口,重置预览位移
166
+ * @param target - 绝对索引或 `{ delta }` 相对位移(越界由 clamp 收敛,菜单为空时无操作)
167
+ */
168
+ private moveCursor;
169
+ /**
170
+ * 当前光标默认的编辑目标
171
+ * @returns 聚焦预设选项时为该选项,否则为题目整体
172
+ */
173
+ private getEditTargetForCurrentSelection;
174
+ /**
175
+ * 打开补充说明编辑器(选项专属或题目整体)
176
+ * @param target - 编辑目标;缺省时按当前聚焦项推导(题目缺失或禁用补充说明时无操作)
177
+ */
178
+ private openCustomEditor;
179
+ /** 关闭编辑器并还原非输入模式状态(清空缓冲与焦点;幂等) */
180
+ private closeEditor;
181
+ /**
182
+ * 确认当前题并跳转下一题 / 总览页(聚焦预设选项时先经模型写入选中)
183
+ * @param currentItem - 聚焦菜单项
184
+ * @param q - 当前题目(未达提交下限时仅提示状态栏,不切页)
185
+ */
186
+ private confirmAndAdvance;
187
+ /**
188
+ * Component 接口约定:键盘事件三级路由(输入模式 → 全局键 → 分视图)
189
+ * Esc 与 Ctrl+C 同为取消键(宿主不会自行中止工具信号):常规模式直接 `finish(true)`,输入模式下编辑器非空时首次仅武装丢弃确认
190
+ * @param data - 终端按键序列
191
+ */
192
+ handleInput(data: string): void;
193
+ /**
194
+ * 题目作答页常规键路由(移动 / 勾选 / 前进 / 补充说明;子路由均为先匹配先消费)
195
+ * @param data - 终端按键序列
196
+ */
197
+ private handleQuestionInput;
198
+ /**
199
+ * 补充说明快捷键路由(`n`/`e` 补充当前聚焦项、`N` 题目整体补充、`x`/Delete 清空)
200
+ * @param q - 当前题目
201
+ * @param data - 按键序列
202
+ * @returns 是否已消费该按键
203
+ */
204
+ private handleNoteShortcut;
205
+ /**
206
+ * 预览区垂直滚动键(PgUp / PgDn / 方括号 / Alt+上下)
207
+ * 滚动位移归一化归属输入路径:以渲染帧写入的 {@link previewViewportObservation} 为界经 `clampWindowOffset` 收敛(缺观察元数据时按单栏预览高度退化)
208
+ * @param data - 按键序列
209
+ * @returns 是否已消费该按键
210
+ */
211
+ private handlePreviewScroll;
212
+ /**
213
+ * 选项菜单光标移动键(↑↓ / jk / Home / End)
214
+ * @param data - 按键序列
215
+ * @returns 是否已消费该按键
216
+ */
217
+ private handleCursorNavigation;
218
+ /**
219
+ * 多选模式下 `a` 键全选 / 清空已选项
220
+ * @param q - 当前题目
221
+ * @param data - 按键序列
222
+ * @returns 是否已消费该按键
223
+ */
224
+ private handleSelectAllShortcut;
225
+ /**
226
+ * 触发聚焦条目主动作(唯一入口为空格键:选项勾选 / 打开补充说明 / 推进确认)
227
+ * @param currentItem - 聚焦菜单项
228
+ * @param q - 当前题目(选项命中上限时仅提示状态栏)
229
+ */
230
+ private activateMenuItem;
231
+ /**
232
+ * 总览页键路由(滚动与回车提交;`Ctrl+S` 同义提交)
233
+ * 空格更改为向下滚动(防止习惯性翻页误提交);位移归一化归属本路径,上界获取自 `summaryCachedLines` 总行数与 `summaryPageSize`,`End` 仅设置超大值,由后续输入收敛
234
+ * @param data - 按键序列
235
+ */
236
+ private handleSummaryInput;
237
+ /**
238
+ * 计算总览页滚动步长
239
+ * @param data - 按键序列
240
+ * @returns 行位移(正下负上,0 为无动作)
241
+ */
242
+ private summaryScrollStep;
243
+ /**
244
+ * Component 接口约定:渲染整屏行(带整屏缓存)
245
+ * 缓存键绑定真实列宽、布局列宽与终端行数(窄于 20 列时二者截断范围不同,仅绑定 layoutWidth 会串帧;行数变化必须整体重新计算);本方法及可达被调函数绝不写入语义状态,仅刷新透明备忘 / 观察缓存
246
+ * @param width - 终端真实可视宽度(布局宽度下限 20 列)
247
+ * @returns 不超过 width 的终端行数组(命中缓存时直接返回)
248
+ */
249
+ render(width: number): string[];
250
+ /**
251
+ * 原地追加底部状态通知行(无文案时不追加,仅包含通知行与前面的空行)
252
+ * @param lines - 输出行数组(原地追加)
253
+ */
254
+ private pushStatusMessage;
255
+ /**
256
+ * 渲染答卷总览与确认提交页(完成状态 / 已选项 / 补充说明 / 提交指引)
257
+ * 内容行由 `view-summary.ts` 构建并按 `(列宽, revision)` 缓存于 `summaryCachedLines`,本方法只做 O(1) 切片;位移在本地收敛,绝不写回 `summaryScrollOffset`(写入只发生在输入路径)
258
+ * @param lines - 输出行数组
259
+ * @param renderWidth - 终端布局列宽
260
+ */
261
+ private renderSubmitSummary;
262
+ /**
263
+ * 渲染补充说明编辑器全屏模式(标题 / 上下文背景 / 编辑框)
264
+ * @param lines - 输出行数组
265
+ * @param renderWidth - 终端可视宽度
266
+ */
267
+ private renderInputMode;
268
+ /**
269
+ * 渲染常规题目作答视图(自适应双栏分屏或单栏紧凑列表)
270
+ * @param lines - 输出行数组
271
+ * @param renderWidth - 终端可视宽度
272
+ */
273
+ private renderQuestionView;
274
+ /**
275
+ * 获取聚焦条目的预渲染完整内容行(`previewCache` 透明备忘缓存,跨条目 / 切换题目零重复解析)
276
+ * @param item - 目标菜单项
277
+ * @param customText - 题目整体补充说明
278
+ * @param optionNote - 选项专属补充说明
279
+ * @param targetWidth - 预览可用列宽
280
+ * @returns 未切片的完整内容行数组;`item` 缺省时返回空数组
281
+ */
282
+ private previewContentLines;
283
+ /**
284
+ * 计算并切片本帧预览框行(纯计算,不写回语义状态)
285
+ * 位移由 `previewScrollOffset` 与本帧总行数在本地收敛;本帧观察元数据写入 {@link previewViewportObservation} 供输入路径归一化位移
286
+ * @param item - 目标菜单项
287
+ * @param customText - 题目整体补充说明
288
+ * @param optionNote - 选项专属补充说明
289
+ * @param targetWidth - 预览可用列宽
290
+ * @param viewHeight - 视口最大行数
291
+ * @returns 本帧可视内容行(含底部滚动信息行)
292
+ */
293
+ private buildPreviewBox;
294
+ /**
295
+ * 对预渲染完整内容行做 O(1) 滑动窗口切片并附加底部行号信息
296
+ * @param fullLines - 已折行完整内容行
297
+ * @param scrollOffset - 已由 {@link clampWindowOffset} 收敛的起始行位移
298
+ * @param viewHeight - 视口最大行数
299
+ * @param targetWidth - 预览可用列宽
300
+ * @returns 视口内容行与滚动信息行
301
+ */
302
+ private slicePreviewBoxLines;
303
+ /**
304
+ * 渲染双栏视图(左选项菜单 + 右推演 / 分镜预览框)
305
+ * @param lines - 输出行数组
306
+ * @param q - 当前题目
307
+ * @param st - 作答状态
308
+ * @param items - 菜单项
309
+ * @param renderWidth - 终端可用宽度(不足侧补空行至两栏等高)
310
+ */
311
+ private renderSplitView;
312
+ /**
313
+ * 遍历输出菜单项行,单栏模式下在聚焦项下方内联展开预览
314
+ * @param lines - 输出行数组
315
+ * @param items - 菜单项
316
+ * @param q - 当前题目
317
+ * @param st - 作答状态
318
+ * @param rowWidth - 选项行可用列宽
319
+ * @param expandFocused - 是否展开聚焦项预览(窗口外条目不访问)
320
+ */
321
+ private pushListItems;
322
+ }