明确用户内容是字面文本,还是标记格式
本文目录
要求 Agent“正确显示笔记”之前,先决定笔记的含义。<b>Roadmap</b> 应显示为这些原样字符,还是生成一个加粗单词?两种产品意图都可以理解,但需要不同的渲染约定。
在本例的虚构 Fieldnote 应用中,第一版按字面显示新建的纯文本笔记。另一份格式化方案暂未决定。既有格式化笔记保留原来的显示路径,不被悄悄重新解释为纯文本。
写出最小而有用的内容约定
新的只读预览接收一条包含三个字段的记录:
{"id":"N1","format":"plain","body":"<b>Roadmap</b>"}
body 在应用正常加载记录之后,已经是 JavaScript 字符串。本文从这个内存边界开始,不定义服务器协议,也不改变已存储笔记。
本版中,format="plain" 表示显示 body 的字符,不把它们当作 HTML 或 Markdown。不要先裁剪字符、解码类似实体的序列,或经过格式解析器。显示时可以对长行自动折行,但要保留明确的换行和连续空格。
空字符串表示一份有意留空的笔记。null 或缺失的 body 表示数据不可用,应显示另一条消息。区分两者,才能避免缺失值看起来像一份已成功加载的空白笔记。
用无害输入比较两种意图
下列示例是人为构造的,不含可执行载荷。最后一列描述未来某个可能的格式化模式所期望的外观,不是已实现的解析器或已批准的语法。
| 输入字符串 | 第一版选定的纯文本显示 | 未来可能的格式化意图 |
|---|---|---|
<b>Roadmap</b> |
标签和单词都原样显示 | 把 Roadmap 显示为强调文字,并为该格式定义表示方式 |
Research & review |
原样显示输入的 & | 显示相同的可见短语 |
Say "ready"; keep 'draft' |
保留两种引号 | 显示相同的可见短语 |
First line + LF + Second line |
显示为两行 | 按选定格式语法保留一个明确换行 |
Research & review |
把 & 当作五个字面字符显示 |
承诺显示方式前,先决定实体语法有没有意义 |
**Roadmap** |
显示星号 | 决定格式是否包含 Markdown 式强调 |
前四行组成基础测试材料:尖括号、&、引号和换行。额外两行可以发现一个容易忽略的错误:在赋值“纯文本”内容之前,先解码或处理 Markdown,就已经改变了含义。
不要把两列都实现,再根据笔记里出现什么字符来猜选哪一列。用户可能有意在普通笔记里写 HTML 或 Markdown 示例。应由记录声明的模式决定走哪条路径。
画清输入到显示的边界
已加载的记录
|
+-- format 是 plain,body 是字符串
| -> 原始字符串 -> 专用 note-body 元素的 textContent
| -> 显示样式保留空白和明确换行
|
+-- format 是 plain,body 是 ""
| -> 空 note-body + 单独的 "No note yet." 状态
|
+-- format 是 plain,body 是 null/缺失/非字符串
| -> 空 note-body + 单独的 "Note unavailable." 状态
|
+-- format 是 formatted
| -> 不在这里重新解释 body
| -> 显示 "Open this note in the existing formatted view."
| -> 保留既有格式化路径和已存储记录
|
+-- 其他任何 format
-> 空 note-body + "Unsupported note format."
空字符串也是字符串;实施时,它属于字符串分支中的空值子情况。这里把它单独列出,是因为周围的状态说明不同。
MDN 的 textContent 文档区分了文本赋值与 HTML 赋值,也指出设置该属性会替换节点的子节点。应使用专用内容元素,避免更新笔记时移除旁边的按钮或状态控件。
这个选择仅针对普通 DOM 文本容器。URL 目标、样式表、脚本、HTML 属性或其他输出上下文,需要各自的处理方式;这次文本赋值不是全应用安全保证。
给 Agent 一份纯文本示例
这段拟议的小型渲染器不使用解析器、网络请求或外部依赖。note-body 是普通段落,独立的状态元素也是普通段落。
<p id="note-status"></p>
<p id="note-body" style="white-space: pre-wrap; overflow-wrap: anywhere;"></p>
const bodyNode = document.getElementById("note-body");
const statusNode = document.getElementById("note-status");
function renderNote(record) {
bodyNode.textContent = "";
statusNode.textContent = "";
if (!record || record.format == null) {
statusNode.textContent = "Note unavailable.";
return;
}
if (record.format === "formatted") {
statusNode.textContent = "Open this note in the existing formatted view.";
return;
}
if (record.format !== "plain") {
statusNode.textContent = "Unsupported note format.";
return;
}
if (typeof record.body !== "string") {
statusNode.textContent = "Note unavailable.";
return;
}
bodyNode.textContent = record.body;
if (record.body === "") statusNode.textContent = "No note yet.";
}
对于 N1,拟议结果是在正文中显示字面字符串 <b>Roadmap</b>。这份笔记不会生成一个加粗的子元素。对于包含一个换行符的笔记,字符串保留该换行,选定的空白样式要求布局把它显示出来。可见折行和字体外观仍需在浏览器中检查。
示例只为已有格式化笔记显示一条消息,没有实现跳转到该视图。应用应保留既有的记录打开方式。把旧格式化笔记交给这个新渲染器,不等于迁移格式。
把第一次交付限制在明确范围内
为虚构 Fieldnote 应用构建新的纯文本笔记预览。
以 literal-note-cases.json 和给定的小型渲染器作为提案。
首个检查目标:macOS 26 上的 Safari 26,使用普通 HTML 段落容器,
不使用框架或第三方格式化库。
这是选定的验证目标,不表示已经安装或测试。
报告浏览器结果前记录精确构建版本。
对于 format=plain 且 body 为字符串的情况,用 textContent
把完整原字符串保留在专用正文元素中。布局保留明确的 LF 换行和空格。
不要先解析 HTML/Markdown,也不要解码类似实体的文字。
状态内容和控件放在这个可被替换的正文元素之外。
空字符串对应空正文和单独的 "No note yet." 状态。
plain body 为 null、缺失或非字符串时,是 "Note unavailable."。
不支持的格式是 "Unsupported note format."。
对于 format=formatted,显示既有视图消息,保持该记录
及其已有显示路径不变。不要迁移或重写。
使用六种无害字符串情况,以及空、null、缺失和旧格式情况。
同时比较正文 textContent 和可见呈现。
验证切换记录时会清除陈旧的正文/状态内容。
持久化、链接、URL/属性处理和既有格式化渲染不在本次范围内。
返回改动和实际观察。
之后的格式化决定:
先明确创作内容的表示方式,以及允许的格式功能。
定义怎样处理不支持的内容,再选择适合这种表示方式的解析和净化处理,
并复核输出上下文。不要为了完成首次交付而临时增加一个 HTML 分支。
这些格式化问题仍是待定的产品和实施工作。设计示意图中的加粗外观,不能决定接受哪种语言、怎样存储,或允许渲染器接受哪些内容。
分开检查表示内容和显示外观
这些是拟议的浏览器检查。作者尚未运行 DOM 渲染器,也没有检查渲染后的页面。
| 检查项 | 预期 | 实际观察 | 状态 |
|---|---|---|---|
| 尖括号和 Markdown 示例 | 正文文字与输入相同;不推断格式 | NOT_RUN | |
| &、类似实体的文字和引号 | 字符保持不变 | NOT_RUN | |
| LF 和连续空格 | 原文字保留;布局保留预期换行/空格 | NOT_RUN | |
| 空字符串与 null/缺失 | 空笔记状态不同于数据不可用状态 | NOT_RUN | |
| 既有格式化记录 | 新正文保持空;出现既有视图消息 | NOT_RUN | |
| 从长笔记切换到空或不可用笔记 | 之前的文字被清除 | NOT_RUN | |
| 更新正文 | 附近控件保留完好 | NOT_RUN |
只检查截图,可能漏掉源字符串被改动;只检查字符串,可能漏掉换行被合并。请同时给 Agent 两种预期,以及需要保留的明确格式边界。