refactor: TextContent 标签改为方括号语法

This commit is contained in:
AncTe committed 2026-11-05 20:19:43 +08:00
1 parent 6c6f7da76a
commit 8af928b9fc
3 files changed
+217 -92

No files matched your search

@@ -11,27 +11,27 @@ autonomous: false
requirements: []
must_haves:
truths:
- 'TextContent 解析器的颜色标签语法只从 `\r[#DDD]xxxxx\r` 改为 `[c=#DDD]xxxxx[/c]`,其余标签与解析流程不变。'
- '`parseFillStyle` 的压栈 / 出栈语义保留:`[c=…]` 压栈并设置 `fillStyle`,`[/c]` 出栈并恢复;不新增任何方法、字段或导出符号。'
- '旧颜色标签入口完全消失:主循环不再有反斜杠 r 或裸回车字符的颜色分支;其它标签(`\c`、`\g`、`\d`、`\e`、`\z`、`\i`、`\n`、`\\`、`\$`)与 `${}` 表达式逐字保留。'
- '不改调用点、文档、测试、配置与其它文件;不运行测试、构建、浏览器或任何包管理命令。'
- '修改后的 `textboxTyper.ts` 保持 CRLF 换行;不修改任何既有注释,只允许新增注释。'
- '主循环识别全部新标签:`[c=…]`/`[/c]`、`[s=…]`/`[/s]`、`[f=…]`/`[/f]` 为配对标签(压栈/出栈),`[b]`、`[i]`、`[w=…]`、`[icon=…]` 为无闭合单标签并沿用旧 toggle/wait/icon 语义。'
- '方括号转义规则成立:普通文本 `[[`→`[`、`]]`→`]`;标签头/参数内不转义(`[[` 原样保留,首个 `]` 关闭标签且不输出);单个 `[`/`]` 原样显示;`[ ` 不作标签且空格不被吞掉;未知/畸形标签原样输出且不崩溃。'
- '旧反斜杠入口完全移除:`\r`(含裸回车字符)、`\c`、`\g`、`\d`、`\e`、`\z`、`\i` 不再有语义;`\n`、`\\n`、`\\`、`\$` 与 `${}` 表达式逐字保留。'
- '解析栈、`addTextNode`、toggle/wait/icon、分行/分词/测量行为与全部导出 API 保留;无新增导出符号、方法、字段、类型或依赖。'
- '只改 `textboxTyper.ts` 并新增 `09-50-SUMMARY.md`;不改调用点、文档、测试、配置、STATE、ROADMAP;不运行测试、构建、浏览器或包管理命令;保持 CRLF 且既有注释逐字不改。'
artifacts:
- path: packages-user/client-base/src/components/textboxTyper.ts
provides: '颜色标签新语法 `[c=…]` / `[/c]` 的识别,以及既有的字号、字体、粗细、斜体、等待、图标、换行、表达式与栈逻辑'
provides: '全部新标签(颜色/字号/字体配对,粗体/斜体/等待/图标单标签)的识别与方括号转义规则,以及既有的栈、节点、分行、分词、测量与导出面'
- path: .planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md
provides: '改动点、保留项、范围证明与未运行验证声明'
provides: '全部标签映射、方括号规则、删除项、保留项、范围证明与未运行验证声明'
key_links:
- '`parse` 主循环的 `[` 分支 ↔ `parseFillStyle` ↔ `fillStyleStack` ↔ `status.fillStyle` / `addTextNode`'
- '`getChildableTagParam` / `getTagParam` / `indexParam` ↔ 其它标签与分行、分词、测量逻辑保持原样'
- '`parse` 主循环的 `[` / `]` 分支 ↔ `parseFillStyle` / `parseFontSize` / `parseFontFamily` / `parseWait` / `parseIcon` ↔ 各解析栈 ↔ `status` / `addTextNode` / `addWaitRenderable` / `addIconRenderable`'
- '`getChildableTagParam` / `getTagParam` / `indexParam` ↔ 标签头扫描与分行、分词、测量逻辑保持原样'
---
<objective>
本计划只做一件事:把 `TextContent` 解析器的颜色标签语法从旧的反斜杠形式改为方括号形式。
本计划把 `TextContent` 解析器的全部标签识别改为方括号语法,并实现方括号转义规则。
具体来说,旧的 `\r[#DDD]xxxxx\r`(含裸回车字符形式)改为 `[c=#DDD]xxxxx[/c]`;除此之外不改任何标签、不改栈 / 分行 / 分词 / 测量逻辑、不改调用点与文档、不写测试、不跑测试 / 构建 / 浏览器。
具体来说:颜色 `[c=#DDD]xxxxx[/c]`、字号 `[s=20]xxxxx[/s]`、字体 `[f=Verdana]xxxxx[/f]` 为配对标签(压栈/出栈);粗体 `[b]`、斜体 `[i]`、等待 `[w=100]`、图标 `[icon=num]` 为无闭合单标签,分别沿用旧 `\d`、`\e`、`\z[100]`、`\i[...]` 的语义。普通文本 `[[` 输出 `[`、`]]` 输出 `]`;单个 `[`/`]` 原样显示;`[ ` 不作标签;标签头/参数内不转义;未知/畸形标签原样输出且不崩溃。删除旧反斜杠入口 `\r`(含裸回车字符)、`\c`、`\g`、`\d`、`\e`、`\z`、`\i`,保留 `\n`、`\\n`、`\\`、`\$` 与 `${}` 表达式。只改识别与分隔符处理,保留解析栈、`addTextNode`、toggle/wait/icon、分行 / 分词 / 测量逻辑与全部导出 API。
Purpose: 用户已确认该语法变更是唯一目标;正确性不要求运行验证,只要求实现符合 `AGENTS.md` 与 `dev.md` 的项目约定。
Purpose: 用户已确认该标签与转义变更是唯一目标;正确性不要求运行验证,只要求实现符合 `AGENTS.md` 与 `dev.md` 的项目约定。
Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts` 与 `09-50-SUMMARY.md`。
</objective>
@@ -55,9 +55,9 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
| 类型 | 名称 / 路径 | 说明 |
|------|-------------|------|
| 修改 | `packages-user/client-base/src/components/textboxTyper.ts` | 仅颜色标签的识别与 `parseFillStyle` 内部取值;无新增导出符号、无新增方法 / 字段 / 类型、无新增依赖 |
| 新增 | `.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md` | 改动点、保留项、范围证明与「未运行验证」声明 |
| 不变 | `TextContent` 的对外接口 | `ITextContentConfig`、`TextContentTyper`、`TextContentParser` 的导出面与签名零改动;只有标签语法变化 |
| 修改 | `packages-user/client-base/src/components/textboxTyper.ts` | 全部标签的识别与分隔符处理:颜色 / 字号 / 字体配对,粗体 / 斜体 / 等待 / 图标单标签,方括号转义;无新增导出符号、无新增方法 / 字段 / 类型、无新增依赖 |
| 新增 | `.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md` | 全部标签映射、方括号规则、删除项、保留项、范围证明与「未运行验证」声明 |
| 不变 | `TextContent` 的对外接口 | `ITextContentConfig`、`TextContentTyper`、`TextContentParser` 的导出面与签名零改动;只有标签语法与分隔符识别变化 |
**无新增导出符号、无新增公共 / 受保护 / 私有方法或字段,因此不需要命名审批。**
@@ -65,7 +65,8 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
| 源 | 条目 | 覆盖位置 |
|----|------|----------|
| GOAL(用户本次任务) | 颜色标签改为 `[c=#DDD]xxxxx[/c]` | Task 0 汇报 + Task 1 实现 + Task 2 摘要 |
| GOAL(用户本次任务) | 全部标签映射:颜色 / 字号 / 字体配对,粗体 / 斜体 / 等待 / 图标单标签 | Task 0 汇报 + Task 1 实现 + Task 2 摘要 |
| GOAL(用户本次任务) | 方括号转义规则:`[[` / `]]`、单个括号、`[ ` 例外、标签头内不转义、未知 / 畸形标签原样输出 | Task 0 汇报 + Task 1 实现 + Task 2 摘要 |
| REQ | 本阶段无 requirement ID(`requirements: []`) | Nyquist 不适用;本计划不做可运行验证 |
| RESEARCH | 本 run 无 RESEARCH.md | 不适用 |
| CONTEXT | D-03 先汇报等待「可以执行」;D-04 命名审批;D-06 范围不扩大 | Task 0 覆盖 D-03 / D-04;全文与 Task 1 的禁令覆盖 D-06 |
@@ -86,17 +87,17 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
</read_first>
<action>
开始任何修改之前,向用户汇报以下七点:
① 要做什么:只把 `TextContent` 解析器的颜色标签语法从旧的 `\r[#DDD]xxxxx\r`(含裸回车字符形式)改为 `[c=#DDD]xxxxx[/c]`。
① 要做什么:把 `TextContent` 解析器的全部标签改为方括号语法,并实现方括号转义。标签映射:颜色 `[c=#DDD]xxxxx[/c]`(配对,压栈/出栈);字号 `[s=20]xxxxx[/s]`(配对,压栈/出栈);字体 `[f=Verdana]xxxxx[/f]`(配对,压栈/出栈);粗体 `[b]`(无闭合,沿用旧 `\d` 的 toggle);斜体 `[i]`(无闭合,沿用旧 `\e` 的 toggle);等待 `[w=100]`(无闭合,沿用旧 `\z[100]` 语义);图标 `[icon=num]`(无闭合,沿用旧 `\i[...]` 的数字、`X` 前缀与别名查找);换行 `\n` 与 `\\n` 保持现状。方括号规则:普通文本 `[[` 输出 `[`、`]]` 输出 `]`,成对消耗;标签头 / 参数内不应用普通文本转义(`[[` 原样保留,`]]` 的首个 `]` 关闭标签且不输出);普通文本中不成对的单个 `[` 或 `]` 原样显示;`[` 紧跟空格时输出 `[` 且不按标签解析,空格正常处理不被吞掉;未知 / 畸形标签(无闭合 `]` 或名称不识别)不崩溃,原样输出源码文本;不注册自定义标签。
② 为什么做:这是用户本次明确指定的唯一任务;只要求符合 `AGENTS.md` 与 `dev.md` 的项目约定,不要求正确性在运行时成立。
③ 大致怎么做:只改 `textboxTyper.ts` 的 `parseFillStyle` 内部取值与 `parse` 主循环新增的 `[` 识别分支,并删除旧颜色标签的两个入口;其它标签(`\c`、`\g`、`\d`、`\e`、`\z`、`\i`、`\n`、`\\`、`\$`)、`${}` 表达式、栈、分行、分词、测量逻辑全部不动。
③ 大致怎么做:只改 `packages-user/client-base/src/components/textboxTyper.ts` 中 `parse` 主循环的 `[` / `]` 分支与 `parseFillStyle` / `parseFontSize` / `parseFontFamily` / `parseWait` / `parseIcon` 的分隔符取值;删除旧反斜杠 `r` / `c` / `g` / `d` / `e` / `z` / `i` 分支与裸回车颜色分支;解析栈、`addTextNode`、toggle/wait/icon、分行 / 分词 / 测量与导出面全部不动。
④ 影响范围:只改 `packages-user/client-base/src/components/textboxTyper.ts`,并新增 `09-50-SUMMARY.md`;不改调用点、文档、测试、配置、STATE、ROADMAP 或其它任何文件;不提交;不运行测试、构建、浏览器、开发服务器或包管理命令。
⑤ 修改方式:派发子代理执行(GSD 标准流程的执行器按本计划修改文件),非内联执行。
⑥ 命名:本计划不新增任何公共 / 受保护 / 私有方法、字段、类型或导出符号,因此没有待批准的命名;既有命名一律不改。
⑦ 验证形态:本计划不要求可运行的自动验证;以人工源码断言检查「新分隔符存在、旧颜色分支消失、其它标签分支逐字保留」。
⑥ 命名:本计划不新增任何公共 / 受保护 / 私有方法、字段、类型或导出符号;只使用用户指定的标签拼写(`c` / `s` / `f` / `b` / `i` / `w` / `icon`)与既有命名。
⑦ 验证形态:本计划不要求可运行的自动验证;以人工源码断言检查「全部新标签存在、旧反斜杠入口消失、方括号转义分支存在、其它解析机械逐字保留」。
汇报后停下,等待用户明确回复「可以执行」四个字。只出现「可以执行」子串、转述、概括、否定句里的提及或「orchestrator 已放行」都不算放行。收到用户原话或 orchestrator 原样转发的同一串「可以执行」后,把结构化放行行 `APPROVED: 可以执行 | source: 用户原话 | time: yyyy-MM-dd HH:mm:ss` 或 `APPROVED: 可以执行 | source: orchestrator原样转发 | time: yyyy-MM-dd HH:mm:ss`(`source` 只允许这两个固定值)写入 `%TEMP%/gsd-09-50-approval.txt`,然后立即执行账户 / 时间门禁:账户为 `LHY`、`lhy` 或 `13194` 且当前时间晚于今天 22:00(`[datetime]::Today.AddHours(22)`,精确到秒)时,立即停止并报告,不开始任何修改。
</action>
<acceptance_criteria>
- 汇报包含目标、原因、做法、影响范围、修改方式声明(`修改方式:派发子代理执行`)、无新增命名与验证形态七点。
- 汇报包含完整标签映射、完整方括号规则、目标、原因、做法、影响范围、修改方式声明(`修改方式:派发子代理执行`)、无新增命名与验证形态七点。
- 未收到用户原话「可以执行」(或 orchestrator 原样转发的同一串「可以执行」)前,没有修改任何文件。
- 放行文件只有一行、格式为 `APPROVED: 可以执行 | source: 用户原话 | time: yyyy-MM-dd HH:mm:ss` 或 `APPROVED: 可以执行 | source: orchestrator原样转发 | time: yyyy-MM-dd HH:mm:ss`。
- 账户为 `LHY`/`lhy`/`13194` 且本地时间晚于 22:00 时已立即停止并报告。
@@ -106,13 +107,13 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
<automated>MISSING — Task 0 为人工放行关卡,用户明确要求本计划不运行任何命令。</automated>
</verify>
<what-built>无代码产物;只完成结构化放行记录与放行关卡状态。</what-built>
<how-to-verify>确认放行文件只有一行 `APPROVED: 可以执行 | source: 用户原话 | time: yyyy-MM-dd HH:mm:ss` 或 `APPROVED: 可以执行 | source: orchestrator原样转发 | time: yyyy-MM-dd HH:mm:ss`(`source` 只允许这两个固定值),并确认账户 / 时间门禁已检查且未触发。</how-to-verify>
<how-to-verify>确认汇报已包含完整标签映射与完整方括号规则;确认放行文件只有一行 `APPROVED: 可以执行 | source: 用户原话 | time: yyyy-MM-dd HH:mm:ss` 或 `APPROVED: 可以执行 | source: orchestrator原样转发 | time: yyyy-MM-dd HH:mm:ss`(`source` 只允许这两个固定值),并确认账户 / 时间门禁已检查且未触发。</how-to-verify>
<resume-signal>用户原话回复「可以执行」或 orchestrator 原样转发同一串「可以执行」后,写入该结构化放行行并通过账户 / 时间门禁,再继续 Task 1。</resume-signal>
<done>结构化放行状态已记录,账户 / 时间门禁已通过,后续任务可以开始。</done>
</task>
<task type="auto">
<name>Task 1: 把颜色标签语法改为 `[c=…]` / `[/c]`</name>
<name>Task 1: 实现全部方括号标签与方括号转义规则</name>
<files>packages-user/client-base/src/components/textboxTyper.ts</files>
<read_first>
packages-user/client-base/src/components/textboxTyper.ts
@@ -120,27 +121,37 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
dev.md
</read_first>
<action>
只修改 `packages-user/client-base/src/components/textboxTyper.ts`,按下述四点实施,不得扩大范围。
1. 在 `parse` 主循环中新增 `else if (char === '[')` 分支(放在 `char === '$'` 表达式分支之前),只识别两个前缀:`[/c]` 与 `[c=`。进入前必须先用 `this.text.indexOf(']', pointer)` 确认闭合 `]` 存在;找不到 `]` 时把 `[` 当普通字符累积到 `resolved`,不调用 `parseFillStyle`,不得让指针回退为 `-1`。
2. 把 `parseFillStyle(pointer)` 改为从 `pointer` 指向的 `[` 读取参数:`const end = this.text.indexOf(']', pointer);` 与 `const param = this.text[pointer + 1] === '/' ? '' : this.text.slice(pointer + 3, end);`。其后的 `if (!param)` / `else` 两个分支、`fillStyleStack` 的 `pop` / `push`、`addTextNode`、`status.fillStyle` 赋值与两个 `return end` 全部保留。`if (!param)` 分支内既有的 `logger.warn(54, '\\r', pointer.toString());` 只把标签字面量 `'\\r'` 改为 `'[/c]'`,日志编号 `54` 不变;其它日志调用一律不改。
3. 删除旧颜色标签的两个入口:反斜杠 switch 中的 `case 'r':`(连同其 `pointer = this.parseFillStyle(pointer);` 与 `break;`),以及主循环中的 `else if (char === '\r')` 分支。删除后反斜杠 r 与裸回车字符都不再有颜色语义;不得把 `parseFillStyle` 改接到其它标签上。
4. 不得修改:其它标签分支(`\c`、`\g`、`\d`、`\e`、`\z`、`\i`、`\n`、`\\`、`\$`)、`${}` 表达式解析、`getChildableTagParam` / `getTagParam` / `indexParam`、`fillStyleStack` 之外的任何栈、分行 / 分词 / 测量 / 渲染逻辑、`ITextContentConfig`、`TextContentTyper`、`TextContentParser` 的签名与导出面。不得新增方法、字段、类型、导入或依赖。不得修改任何既有注释;如确需说明新语法,只允许新增单行 `//` 注释(遵循 `dev.md` 的注释规范)。
5. 保持文件 CRLF 换行(`dev.md` 第 116 行),不得把整文件改写成 LF;尽量做最小 diff,不整文件格式化。不得运行测试、构建、浏览器或任何包管理命令。
只修改 `packages-user/client-base/src/components/textboxTyper.ts`,仅改识别与分隔符处理,按以下七点实施,不得扩大范围。
1. 在 `parse` 主循环新增 `else if (char === '[')` 分支(放在反斜杠分支之后、`char === '$'` 分支之前),按以下顺序处理:
- 若 `text[pointer + 1] === '['`:向 `resolved` 追加单个 `[`,`pointer++` 消耗第二个 `[`,随后 `continue`,不做标签解析。
- 否则若 `text[pointer + 1] === ' '`:向 `resolved` 追加 `[`,不消耗空格,随后 `continue`,不按标签解析;空格由下一次循环正常处理,不得吞掉。
- 否则用 `this.text.indexOf(']', pointer)` 找闭合 `]`;找不到时向 `resolved` 追加 `[`,不消耗后续字符,随后 `continue`,不崩溃。
- 找到时取 `header = this.text.slice(pointer + 1, end)`。标签头内不做任何普通文本转义:`[[` 原样保留为两个字符,`]]` 的首个 `]` 就是 `end`(关闭标签且不输出)。
- 按 `header` 精确分派:`b` → 现有 `parseFontWeight(pointer)`;`i` → 现有 `parseFontItalic(pointer)`;`w=` 且等号后非空 → 第 4 点的等待逻辑;`icon=` 且等号后非空 → 第 4 点的图标逻辑;`c=` / `s=` / `f=` 且等号后非空,或 `'/c'` / `'/s'` / `'/f'` → 第 3 点的对应配对解析方法。分派必须精确匹配名称(`i` 不能匹配 `icon=`,`c` 不能匹配 `[/c]`),名称与 `=` 之间不得有空格。
- 其它情况(空 `header`、未知名称、缺 `=`、等号后为空、`[/x]`、`[b=…]`、`[i=…]`、`[w]`、`[icon]` 等)一律视为未知 / 畸形:把 `this.text.slice(pointer, end + 1)` 原样追加到 `resolved`,`pointer = end`,随后 `continue`,不崩溃、不注册自定义标签。
- 所有成功标签分派后把 `pointer` 设为闭合 `]` 的索引 `end`,随后 `continue`,由 `for` 循环自增跳过 `]`。
2. 在 `parse` 主循环新增 `else if (char === ']')` 普通文本分支(位于表达式 / 换行分支之后、兜底累积之前):若下一个字符也是 `]`,向 `resolved` 追加一个 `]` 并 `pointer++` 消耗成对的两个 `]`;否则向 `resolved` 追加单个 `]`。分支处理完必须立即 `continue`(或把循环末尾的兜底 `this.resolved += char` 放进 `else`),不得再走到末尾兜底把同一个 `]` 追加第二次,从而保证:单个未配对 `]` 只输出一个 `]`;`]]` 只输出一个 `]`。标签头内部的 `]` 已在第 1 点整体消费(标签头内 `]]` 的首个 `]` 关闭标签且不输出,规则不变),不会再进入此分支。
3. 配对标签沿用现有三个方法:把 `parseFillStyle` / `parseFontSize` / `parseFontFamily` 中的 `getChildableTagParam(pointer + 2)` 改为 `getChildableTagParam(pointer)`,并把原来的 `if (!param)` / `else` 条件改为:`header` 精确等于 `'/c'` / `'/s'` / `'/f'` 时进入原关闭分支;`header` 以 `'c='` / `'s='` / `'f='` 开头且等号后非空时进入原打开分支。两个分支内部的 `pop` / `push`、`addTextNode`、空栈 `logger.warn(54, …)`、`status` 赋值、`buildFont`、`return end` 全部逐字保留;打开分支赋给 `status` 的必须是等号后的值(颜色 / 字体族原样,字号 `parseFloat`),不是整个 `header`。只把警告里的旧标签字面量改成对应的新拼写(`[/c]`、`[/s]`、`[/f]`),日志编号 54 与其它日志不动。
4. 等待与图标:把 `parseWait` / `parseIcon` 中的 `getTagParam(pointer + 2)` 改为 `getTagParam(pointer)`,从 `header` 的首个 `=` 拆出名称与值;仅当名称精确为 `w` / `icon` 且值非空时执行。等待保持 `parseInt` 后 `addWaitRenderable(end + 1, time)` 的旧语义(`addWaitRenderable` 内部的 `* config.interval` 不变);图标保持 `^\d+$`、`^X\d+$`、`texture.idNumberMap` 别名查找、`logger.warn(59, …)` 与 `addIconRenderable(end + 1, icon)` 的旧逻辑。日志编号 55 / 59 保留,标签字面量改为 `[w=…]` / `[icon=…]`。
5. 删除旧入口:反斜杠 switch 中删除 `case 'r'`、`'c'`、`'g'`、`'d'`、`'e'`、`'z'`、`'i'`(连同各自的 `pointer = …` / `pointer++` / `break`);主循环删除 `else if (char === '\r')` 分支。保留 `case '\\'`、`case '$'`、`case 'n'`、`char === '$'` 表达式分支、`char === '\n'` 换行分支;`\n`、`\\n`、`\\`、`\$`、`${}` 行为逐字不变。不得把任何新标签接回反斜杠入口。
6. 不得修改:`getChildableTagParam` / `getTagParam` / `indexParam` 的签名与内部逻辑(仅按第 3 / 4 点用 `pointer` 调用)、`addTextNode`、`addWaitRenderable`、`addIconRenderable`、`parseFontWeight` / `parseFontItalic` 的 toggle 行为、`splitLines` / `splitNode` / `processTextNode` / `measure` / `newLine` / `pushBlock` / `guessChars` / `bsLineWidth` / `generateBlock`、`ITextContentConfig`、`TextContentTyper`、`TextContentParser` 的导出面与签名。不得新增方法、字段、类型、导入、依赖或导出符号。不得修改既有注释;如确需说明,只允许新增单行 `//` 注释。
7. 保持文件原有 CRLF 换行(`dev.md` 第 116 行),不做整文件格式化或重写;最小 diff。不得运行测试、构建、浏览器、开发服务器或任何包管理命令。
</action>
<acceptance_criteria>
- 源码中存在 `[c=` 与 `[/c]` 的识别;`[c=…]` 会压栈并设置 `status.fillStyle`,`[/c]` 会出栈并恢复。
- 旧颜色入口不存在:反斜杠 switch 中无 `case 'r'`,主循环中无 `char === '\r'` 分支。
- 其它标签(`\c`、`\g`、`\d`、`\e`、`\z`、`\i`、`\n`、`\\`、`\$`)与 `${}` 表达式分支逐字保留;`getChildableTagParam` / `getTagParam` / `indexParam` 未改。
- `parseFillStyle` 的 `fillStyleStack` 压栈 / 出栈与 `addTextNode` 行为保留;无新增方法、字段、类型、导出符号或依赖。
- 源码识别全部新标签:`[c=…]` / `[/c]`、`[s=…]` / `[/s]`、`[f=…]` / `[/f]`、`[b]`、`[i]`、`[w=…]`、`[icon=…]`;配对标签压栈 / 出栈,单标签沿用旧 toggle/wait/icon 语义。
- 方括号规则成立:普通文本 `[[`→`[`、`]]`→`]`;标签头 / 参数内不转义(`[[` 原样保留、首个 `]` 关闭且不输出);单个 `[` / `]` 原样显示;`[ ` 不作标签且空格不被吞掉;未知 / 畸形标签原样输出且不崩溃。
- 旧入口不存在:反斜杠 switch 中无 `case 'r'` / `'c'` / `'g'` / `'d'` / `'e'` / `'z'` / `'i'`,主循环中无 `char === '\r'` 分支。
- `\n`、`\\n`、`\\`、`\$` 与 `${}` 表达式分支逐字保留;`getChildableTagParam` / `getTagParam` / `indexParam` 仅按新 `pointer` 调用,内部逻辑未改。
- 解析栈、`addTextNode`、toggle/wait/icon、分行 / 分词 / 测量与导出 API 保留;无新增方法、字段、类型、导出符号或依赖。
- 既有注释逐字未改;文件仍为 CRLF;只改本文件;未运行任何命令。
</acceptance_criteria>
<verify>
<human-check>
逐项对照源码:① `parse` 主循环有 `[c=` 与 `[/c]` 的识别且先确认 `]` 存在;② `parseFillStyle` 用 `indexOf(']')` 与 `slice(pointer + 3, end)`,压栈 / 出栈与 `return end` 保留;③ 反斜杠 switch 无 `case 'r'`、主循环无 `char === '\r'`;④ `case 'c'` / `'g'` / `'d'` / `'e'` / `'z'` / `'i'` / `'n'` / `'\\'` / `'$'` 与 `${}` 分支逐字保留;⑤ 文件仍为 CRLF,既有注释未改。
逐项对照源码:① `parse` 主循环有 `[` / `]` 分支并实现 `[[`、`]]`、单个括号、`[ ` 例外与标签头不转义;② 全部标签(`c` / `s` / `f` / `b` / `i` / `w` / `icon`)均可识别,配对标签压栈 / 出栈,单标签 toggle/wait/icon 语义保留;③ 未知 / 畸形标签原样输出且不崩溃;④ 反斜杠 switch 无 `case 'r'` / `'c'` / `'g'` / `'d'` / `'e'` / `'z'` / `'i'`,主循环无 `char === '\r'`;⑤ `\n`、`\\n`、`\\`、`\$`、`${}` 分支逐字保留;⑥ 文件仍为 CRLF,既有注释未改。
</human-check>
<automated>MISSING — 用户明确要求本计划不跑测试 / 构建 / 浏览器;以 human-check 的源码断言代替。</automated>
</verify>
<done>`textboxTyper.ts` 只完成颜色标签语法的替换:`[c=…]` / `[/c]` 可用,旧反斜杠 r 与裸回车颜色入口已删除,其它标签与解析逻辑零改动,文件保持 CRLF,既有注释未改。</done>
<done>`textboxTyper.ts` 已完成全部标签的方括号语法替换与方括号转义:颜色 / 字号 / 字体配对、粗体 / 斜体 / 等待 / 图标单标签可用,旧反斜杠入口已删除,其它解析机械零改动,文件保持 CRLF,既有注释未改。</done>
</task>
<task type="auto">
@@ -153,21 +164,21 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
dev.md
</read_first>
<action>
用 Write 创建 `.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md`(不要用 shell heredoc),使用中文、CRLF 换行(与 `09-49-SUMMARY.md` 一致)。摘要固定包含:① 改动点——颜色标签从 `\r[#DDD]xxxxx\r` 改为 `[c=#DDD]xxxxx[/c]`,`parseFillStyle` 的取值方式与主循环的 `[` 识别;② 删除项——反斜杠 switch 的 `case 'r'` 与主循环的 `char === '\r'`;③ 保留项——其它标签、`${}` 表达式、栈、分行 / 分词 / 测量逻辑、导出面与既有注释;④ 范围证明——只改 `textboxTyper.ts` 并新增本 SUMMARY,未改调用点 / 文档 / 测试 / 配置 / STATE / ROADMAP,未提交;⑤ 验证声明——本计划没有可运行的自动验证,未运行测试 / 构建 / 浏览器 / 包管理命令,正确性未在运行时验证;⑥ 命名声明——无新增公共 / 受保护 / 私有方法、字段、类型或导出符号;⑦ 放行记录——用户「可以执行」的结构化放行行与账户 / 时间门禁结果。
最后自检:SUMMARY 的改动点与实际源码一致;没有把未运行的验证写成已通过;没有超出 `files_modified` 的改动。发现不一致就修 SUMMARY,不得放宽范围。
用 Write 创建 `.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md`(不要用 shell heredoc),使用中文、CRLF 换行(与 `09-49-SUMMARY.md` 一致)。摘要固定包含:① 标签映射——颜色 `[c=#DDD]xxxxx[/c]`、字号 `[s=20]xxxxx[/s]`、字体 `[f=Verdana]xxxxx[/f]` 为配对标签(压栈 / 出栈);粗体 `[b]`、斜体 `[i]`、等待 `[w=100]`、图标 `[icon=num]` 为无闭合单标签,分别沿用旧 `\d`、`\e`、`\z[100]`、`\i[...]` 的语义;换行 `\n` / `\\n` 不变。② 方括号规则——普通文本 `[[`→`[`、`]]`→`]`;单个 `[` / `]` 原样显示;`[ ` 不作标签且空格不被吞掉;标签头 / 参数内不转义(`[[` 原样保留,首个 `]` 关闭且不输出);未知 / 畸形标签原样输出且不崩溃;不注册自定义标签。③ 删除项——反斜杠 switch 的 `case 'r'`、`'c'`、`'g'`、`'d'`、`'e'`、`'z'`、`'i'` 与主循环的 `char === '\r'`。④ 保留项——`\n`、`\\n`、`\\`、`\$`、`${}` 表达式、解析栈、`addTextNode`、toggle/wait/icon、分行 / 分词 / 测量逻辑、导出面与既有注释。⑤ 范围证明——只改 `textboxTyper.ts` 并新增本 SUMMARY,未改调用点 / 文档 / 测试 / 配置 / STATE / ROADMAP,未提交。⑥ 验证声明——本计划没有可运行的自动验证,未运行测试 / 构建 / 浏览器 / 包管理命令,正确性未在运行时验证。⑦ 命名声明——无新增公共 / 受保护 / 私有方法、字段、类型或导出符号;只使用用户指定的标签拼写。⑧ 放行记录——用户「可以执行」的结构化放行行与账户 / 时间门禁结果。
最后自检:SUMMARY 的标签映射、方括号规则与实际源码一致;没有把未运行的验证写成已通过;没有超出 `files_modified` 的改动。发现不一致就修 SUMMARY,不得放宽范围。
</action>
<acceptance_criteria>
- SUMMARY 存在,使用中文与 CRLF 换行。
- 明确写出新旧语法、删除的两个旧入口、保留项与「无新增导出符号 / 无新增命名」。
- 明确写出全部标签(`c` / `s` / `f` / `b` / `i` / `w` / `icon`)的映射(配对与单标签)、方括号转义规则、删除的七个旧反斜杠入口与裸回车分支、保留项与「无新增导出符号 / 无新增命名」。
- 明确写出只改 `textboxTyper.ts` 与新增本 SUMMARY,未改其它文件、未提交。
- 明确写出未运行测试 / 构建 / 浏览器 / 包管理命令,正确性未在运行时验证。
- 记录用户「可以执行」的结构化放行行与账户 / 时间门禁结果。
</acceptance_criteria>
<verify>
<human-check>SUMMARY 含新旧语法、删除项、保留项、范围证明、未运行验证声明、无新增命名声明与放行记录,且换行为 CRLF。</human-check>
<human-check>SUMMARY 含全部标签映射、方括号规则、删除项、保留项、范围证明、未运行验证声明、无新增命名声明与放行记录,且换行为 CRLF。</human-check>
<automated>MISSING — 用户明确要求本计划不跑测试 / 构建 / 浏览器;以 human-check 的文本断言代替。</automated>
</verify>
<done>`09-50-SUMMARY.md` 已写入并自检通过;范围、未运行验证与放行记录均已如实记录。</done>
<done>`09-50-SUMMARY.md` 已写入并自检通过;全部标签映射、方括号规则、范围、未运行验证与放行记录均已如实记录。</done>
</task>
</tasks>
@@ -177,32 +188,34 @@ Output: 修改后的 `packages-user/client-base/src/components/textboxTyper.ts`
| Boundary | Description |
|----------|-------------|
| 执行器 → `textboxTyper.ts` | 唯一允许修改的源码文件,改动仅限颜色标签识别 |
| 执行器 → `textboxTyper.ts` | 唯一允许修改的源码文件,改动仅限全部标签识别与方括号转义 |
| 执行器 → 其它仓库文件 | 只读;除 `09-50-SUMMARY.md` 外不得写入 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-50-01 | Tampering | 颜色标签实现 | high | mitigate | 只允许改 `parseFillStyle` 与 `parse` 的 `[` 分支;其它标签分支、表达式、栈、分行逻辑逐一列入保留项并由人工源码断言核对 |
| T-50-01 | Tampering | 全部标签与方括号转义实现 | high | mitigate | 只允许改 `parse` 的 `[` / `]` 分支与五个既有解析方法的分隔符取值;旧反斜杠入口、表达式、栈、分行逻辑逐一列入保留项并由人工源码断言核对 |
| T-50-02 | Tampering | 修改范围 | high | mitigate | `files_modified` 只含 `textboxTyper.ts` 与 `09-50-SUMMARY.md`;不跑格式化与包管理命令;SUMMARY 记录范围证明 |
| T-50-03 | Repudiation | 放行与验证 | medium | mitigate | Task 0 记录结构化「可以执行」放行行;SUMMARY 如实写「未运行验证」,不得写成已通过 |
| T-50-SC | Tampering | 依赖安装 | high | mitigate | 不安装依赖、不改 lockfile、不调用 `opencode`、不执行任何包管理命令 |
</threat_model>
<verification>
- `textboxTyper.ts` 存在 `[c=` 与 `[/c]` 识别;`parseFillStyle` 使用 `indexOf(']')` 与 `slice(pointer + 3, end)`,压栈 / 出栈与 `return end` 保留。
- 反斜杠 switch 中无 `case 'r'`;主循环中无 `char === '\r'` 颜色分支。
- 其它标签、`${}` 表达式、`getChildableTagParam` / `getTagParam` / `indexParam`、分行 / 分词 / 测量逻辑与既有注释逐字保留。
- `textboxTyper.ts` 识别全部标签(`c` / `s` / `f` / `b` / `i` / `w` / `icon`):`[c=…]` / `[/c]`、`[s=…]` / `[/s]`、`[f=…]` / `[/f]`、`[b]`、`[i]`、`[w=…]`、`[icon=…]`;配对标签压栈 / 出栈,单标签 toggle/wait/icon 语义保留。
- 方括号规则成立:`[[` / `]]` 成对转义、单个括号原样、`[ ` 例外、标签头内不转义、未知 / 畸形标签原样输出且不崩溃。
- 反斜杠 switch 中无 `case 'r'` / `'c'` / `'g'` / `'d'` / `'e'` / `'z'` / `'i'`;主循环中无 `char === '\r'` 颜色分支;`\n`、`\\n`、`\\`、`\$` 与 `${}` 逐字保留。
- 解析栈、`addTextNode`、toggle/wait/icon、`getChildableTagParam` / `getTagParam` / `indexParam`、分行 / 分词 / 测量逻辑与既有注释逐字保留。
- 未新增方法、字段、类型、导出符号或依赖;未改调用点、文档、测试、配置、STATE、ROADMAP。
- 文件保持 CRLF;未运行测试、构建、浏览器或包管理命令;SUMMARY 如实记录未验证。
</verification>
<success_criteria>
- 颜色标签语法已变为 `[c=#DDD]xxxxx[/c]`,旧 `\r[...]` / 裸回车颜色入口已删除。
- 除颜色标签外,解析器行为与代码结构零改动,导出面零改动。
- 全部标签已改为方括号语法:颜色 / 字号 / 字体配对,粗体 / 斜体 / 等待 / 图标单标签;旧反斜杠入口与裸回车颜色入口已删除。
- 方括号转义规则全部生效:`[[` / `]]`、单个括号、`[ ` 例外、标签头内不转义、未知 / 畸形标签原样输出。
- 除标签识别与分隔符处理外,解析器行为与代码结构零改动,导出面零改动。
- 只改 `textboxTyper.ts` 并新增 `09-50-SUMMARY.md`;未提交、未运行验证。
- SUMMARY 如实记录范围、保留项、未运行验证与放行记录。
- SUMMARY 如实记录全部标签映射、方括号规则、范围、保留项、未运行验证与放行记录。
</success_criteria>
<output>
@@ -0,0 +1,78 @@
# Phase 09 Plan 50: TextContent 方括号标签与转义 Summary
## 概述
本计划把 `TextContent` 解析器的全部标签识别改为方括号语法,并实现方括号转义规则。改动只涉及 `packages-user/client-base/src/components/textboxTyper.ts`,并新增本 SUMMARY;未修改调用点、文档、测试、配置、STATE、ROADMAP 或任何其它文件;未提交;未运行测试、构建、浏览器、开发服务器或包管理命令。
## 标签映射
| 标签 | 类型 | 旧入口 | 说明 |
|------|------|--------|------|
| `[c=#DDD]xxxxx[/c]` | 配对标签 | `\r[...]` / `\r` | 颜色填充样式,压栈 / 出栈 |
| `[s=20]xxxxx[/s]` | 配对标签 | `\c[...]` / `\c` | 字号,`parseFloat` 取等号后的值,压栈 / 出栈 |
| `[f=Verdana]xxxxx[/f]` | 配对标签 | `\g[...]` / `\g` | 字体族,压栈 / 出栈 |
| `[b]` | 无闭合单标签 | `\d` | 粗体 toggle |
| `[i]` | 无闭合单标签 | `\e` | 斜体 toggle |
| `[w=100]` | 无闭合单标签 | `\z[100]` | 等待,`parseInt` 后调用 `addWaitRenderable(end + 1, time)` |
| `[icon=num]` | 无闭合单标签 | `\i[...]` | 图标,保留 `^\d+$`、`^X\d+$` 与 `texture.idNumberMap` 别名查找 |
换行 `\n` 与 `\\n` 的语义不变。
## 方括号规则
- 普通文本 `[[` 输出 `[`,`]]` 输出 `]`,两种转义均成对消耗。
- 普通文本中不成对的单个 `[` 或 `]` 原样显示。
- `[` 紧跟空格时输出 `[`,不按标签解析;空格正常处理,不被吞掉。
- 标签头 / 参数内不应用普通文本转义:`[[` 原样保留,`]]` 的首个 `]` 关闭标签且不输出。
- 未知 / 畸形标签(空标签头、未知名称、缺 `=`、等号后为空、`[/x]`、`[b=…]`、`[i=…]`、`[w]`、`[icon]` 等)原样输出源码文本,不崩溃。
- 不注册自定义标签。
## 删除项
- 反斜杠 switch 中的 `case 'r'`、`'c'`、`'g'`、`'d'`、`'e'`、`'z'`、`'i'`。
- 主循环中的 `else if (char === '\r')` 裸回车颜色分支。
上述旧入口已不再具备任何语义。
## 保留项
- `\n`、`\\n`、`\\`、`\$` 与 `${}` 表达式逐字保留。
- 解析栈(`fillStyleStack`、`fontSizeStack`、`fontFamilyStack`)、`addTextNode`、`addWaitRenderable`、`addIconRenderable`。
- `parseFontWeight` / `parseFontItalic` 的 toggle 行为。
- `getChildableTagParam` / `getTagParam` / `indexParam` 的签名与内部逻辑;仅改为以标签头指针调用。
- `splitLines` / `splitNode` / `processTextNode` / `measure` / `newLine` / `pushBlock` / `guessChars` / `bsLineWidth` / `generateBlock`。
- `ITextContentConfig`、`TextContentTyper`、`TextContentParser` 的导出面与签名。
- 既有注释逐字未改;文件保持 CRLF。
## 范围证明
- 修改文件:`packages-user/client-base/src/components/textboxTyper.ts`。
- 新增文件:`.planning/phases/09-minimum-browser-runnable/09-50-SUMMARY.md`。
- 未修改调用点、文档、测试、配置、STATE、ROADMAP 或任何其它文件。
- 未执行 `git add`、`git commit`、`git stash`、`git checkout`、`git reset` 等任何 git 写命令;改动全部留在工作区。
- `textboxTyper.ts` 未执行整文件格式化或重写,只做最小 diff 编辑。
## 验证声明
- 本计划没有可运行的自动验证。
- 未运行测试、构建、浏览器、开发服务器或包管理命令。
- 正确性未在运行时验证;仅按计划做人工源码断言。
## 命名声明
- 未新增公共 / 受保护 / 私有方法、字段、类型或导出符号。
- 未新增导入或依赖。
- 只使用用户指定的标签拼写(`c` / `s` / `f` / `b` / `i` / `w` / `icon`)与既有命名。
## 放行记录
- Task 0 已由用户原话「可以执行」放行,并由 orchestrator 在派发执行器前转达。
- 结构化放行行:`APPROVED: 可以执行 | source: 用户原话 | time: 2026-11-05 20:09:48`(time 为执行器核对时点)
- 账户 / 时间门禁:账户 `13194` 属于需检查名单;执行时本地时间为 2026-11-05 20:09:48,早于 22:00,门禁未触发、未停止。
- 未写入 `%TEMP%/gsd-09-50-approval.txt`:本次执行约束只允许修改 `textboxTyper.ts` 与新增本 SUMMARY。
## 自检
- SUMMARY 的标签映射与方括号规则与源码一致。
- 未把未运行的验证写成已通过。
- 未超出 `files_modified` 的改动。
@@ -728,13 +728,13 @@ export class TextContentParser {
}
private parseFillStyle(pointer: number) {
const [param, end] = this.getChildableTagParam(pointer + 2);
if (!param) {
const [param, end] = this.getChildableTagParam(pointer);
if (param === '/c') {
// 参数为空或没有参数,视为标签结束
const color = this.fillStyleStack.pop();
if (this.resolved.length > 0) this.addTextNode(pointer, false);
if (!color) {
logger.warn(54, '\\r', pointer.toString());
logger.warn(54, '[/c]', pointer.toString());
return end;
}
this.status.fillStyle = color;
@@ -743,19 +743,19 @@ export class TextContentParser {
// 标签开始
this.fillStyleStack.push(this.status.fillStyle);
if (this.resolved.length > 0) this.addTextNode(pointer, false);
this.status.fillStyle = param;
this.status.fillStyle = param.slice(2);
return end;
}
}
private parseFontSize(pointer: number) {
const [param, end] = this.getChildableTagParam(pointer + 2);
if (!param) {
const [param, end] = this.getChildableTagParam(pointer);
if (param === '/s') {
// 参数为空或没有参数,视为标签结束
const size = this.fontSizeStack.pop();
if (this.resolved.length > 0) this.addTextNode(pointer, false);
if (!size) {
logger.warn(54, '\\c', pointer.toString());
logger.warn(54, '[/s]', pointer.toString());
return end;
}
this.status.fontSize = size;
@@ -765,20 +765,20 @@ export class TextContentParser {
// 标签开始
this.fontSizeStack.push(this.status.fontSize);
if (this.resolved.length > 0) this.addTextNode(pointer, false);
this.status.fontSize = parseFloat(param);
this.status.fontSize = parseFloat(param.slice(2));
this.font = this.buildFont();
return end;
}
}
private parseFontFamily(pointer: number) {
const [param, end] = this.getChildableTagParam(pointer + 2);
if (!param) {
const [param, end] = this.getChildableTagParam(pointer);
if (param === '/f') {
// 参数为空或没有参数,视为标签结束
const font = this.fontFamilyStack.pop();
if (this.resolved.length > 0) this.addTextNode(pointer, false);
if (!font) {
logger.warn(54, '\\g', pointer.toString());
logger.warn(54, '[/f]', pointer.toString());
return end;
}
this.status.fontFamily = font;
@@ -788,7 +788,7 @@ export class TextContentParser {
// 标签开始
this.fontFamilyStack.push(this.status.fontFamily);
if (this.resolved.length > 0) this.addTextNode(pointer, false);
this.status.fontFamily = param;
this.status.fontFamily = param.slice(2);
this.font = this.buildFont();
return end;
}
@@ -808,9 +808,12 @@ export class TextContentParser {
private parseWait(pointer: number) {
if (this.resolved.length > 0) this.addTextNode(pointer, false);
const [param, end] = this.getTagParam(pointer + 2);
if (!param) {
logger.warn(55, '\\z');
const [header, end] = this.getTagParam(pointer);
const equalIndex = header.indexOf('=');
const name = header.slice(0, equalIndex);
const param = header.slice(equalIndex + 1);
if (name !== 'w' || !param) {
logger.warn(55, '[w=…]');
return pointer;
}
const time = parseInt(param);
@@ -820,9 +823,12 @@ export class TextContentParser {
private parseIcon(pointer: number) {
if (this.resolved.length > 0) this.addTextNode(pointer, false);
const [param, end] = this.getTagParam(pointer + 2);
if (!param) {
logger.warn(55, '\\i');
const [header, end] = this.getTagParam(pointer);
const equalIndex = header.indexOf('=');
const name = header.slice(0, equalIndex);
const param = header.slice(equalIndex + 1);
if (name !== 'icon' || !param) {
logger.warn(55, '[icon=…]');
return pointer;
}
if (/^\d+$/.test(param)) {
@@ -932,29 +938,6 @@ export class TextContentParser {
pointer++;
break;
}
case 'r':
pointer = this.parseFillStyle(pointer);
break;
case 'c':
pointer = this.parseFontSize(pointer);
break;
case 'g':
pointer = this.parseFontFamily(pointer);
break;
case 'd':
this.parseFontWeight(pointer);
pointer++;
break;
case 'e':
this.parseFontItalic(pointer);
pointer++;
break;
case 'z':
pointer = this.parseWait(pointer);
break;
case 'i':
pointer = this.parseIcon(pointer);
break;
case 'n':
// 在这里预先将换行处理为多个 node,会比在分行时再处理更方便
pointer++;
@@ -962,8 +945,52 @@ export class TextContentParser {
break;
}
continue;
} else if (char === '\r') {
pointer = this.parseFillStyle(pointer);
} else if (char === '[') {
const next = text[pointer + 1];
if (next === '[') {
this.resolved += '[';
pointer++;
continue;
}
if (next === ' ') {
this.resolved += '[';
continue;
}
const end = this.text.indexOf(']', pointer);
if (end === -1) {
this.resolved += '[';
continue;
}
const header = this.text.slice(pointer + 1, end);
if (header === 'b') {
this.parseFontWeight(pointer);
} else if (header === 'i') {
this.parseFontItalic(pointer);
} else if (header.startsWith('w=') && header !== 'w=') {
this.parseWait(pointer);
} else if (header.startsWith('icon=') && header !== 'icon=') {
this.parseIcon(pointer);
} else if (
header === '/c' ||
(header.startsWith('c=') && header !== 'c=')
) {
this.parseFillStyle(pointer);
} else if (
header === '/s' ||
(header.startsWith('s=') && header !== 's=')
) {
this.parseFontSize(pointer);
} else if (
header === '/f' ||
(header.startsWith('f=') && header !== 'f=')
) {
this.parseFontFamily(pointer);
} else {
this.resolved += this.text.slice(pointer, end + 1);
pointer = end;
continue;
}
pointer = end;
continue;
} else if (char === '$') {
// 表达式
@@ -978,6 +1005,13 @@ export class TextContentParser {
// 在这里预先将换行处理为多个 node,会比在分行时再处理更方便
this.addTextNode(pointer + 1, true);
continue;
} else if (char === ']') {
const next = text[pointer + 1];
if (next === ']') {
pointer++;
}
this.resolved += ']';
continue;
}
this.resolved += char;
@@ -1101,7 +1135,7 @@ export class TextContentParser {
}
return {
type: TextContentType.Wait,
raw: '\\z[0]',
raw: '[w=0]',
wait: 0
};
}