返回 CodeWhale
learning.ts
根目录 / web / lib / ratatui / learning.ts
1 import type { LocalizedText } from "@/lib/content/vocabulary";
2 import { pickText } from "@/lib/i18n/dictionaries";
3 import type { CatalogueEntry } from "./catalogue";
4
5 export interface LearningTask {
6 id: string;
7 label: LocalizedText;
8 description: LocalizedText;
9 families: string[];
10 recommended: string[];
11 }
12
13 /** Intent routes sit beside the generated catalogue so exports remain reproducible. */
14 export const TASKS: LearningTask[] = [
15 {
16 id: "build-app", label: { en: "Build a terminal app", zh: "构建终端应用" },
17 description: { en: "Start with the native shell, composer and workbar, then arrange your own workspace.", zh: "从原生界面框架、输入框与工作栏开始,再组合自己的工作区。" },
18 families: ["studio", "native-views", "native-chrome", "workbar", "scenes"],
19 recommended: ["native-composer", "workbar-tasks", "showcase-work"],
20 },
21 {
22 id: "input", label: { en: "Accept input", zh: "接收输入" },
23 description: { en: "Add editable fields, search, choices, forms and keyboard navigation.", zh: "添加可编辑字段、搜索、选择、表单与键盘导航。" },
24 families: ["input", "chrome"], recommended: ["text-input-typed", "picker-query", "form"],
25 },
26 {
27 id: "conversations", label: { en: "Show conversations", zh: "展示对话" },
28 description: { en: "Render messages, code, attached context, queued input and agents.", zh: "渲染消息、代码、附加上下文、排队输入与智能体。" },
29 families: ["transcript", "components"], recommended: ["message", "transcript-code", "pending-queued"],
30 },
31 {
32 id: "review", label: { en: "Review work", zh: "审查工作" },
33 description: { en: "Explain changes, request a decision and present results supplied by your app.", zh: "说明变更、请求决定,并展示应用提供的结果。" },
34 families: ["display"], recommended: ["diff", "approval-command", "receipt-table"],
35 },
36 {
37 id: "style", label: { en: "Style your terminal", zh: "设计终端风格" },
38 description: { en: "Choose a Codewhale theme, ocean depth and readable terminal details.", zh: "选择 Codewhale 主题、海洋深度与易读的终端细节。" },
39 families: ["tui-palettes", "water", "foundation"], recommended: ["tui-theme-underwater", "ocean-column", "depth"],
40 },
41 {
42 id: "motion", label: { en: "Add motion and life", zh: "添加动画与海洋生物" },
43 description: { en: "Show progress with spinners, or add the whale, fish and jellyfish.", zh: "用加载动画展示进度,或添加鲸鱼、鱼群与水母。" },
44 families: ["motion", "habitat", "whales", "whale-actions"], recommended: ["spinner", "whale-busy", "fish-school"],
45 },
46 ];
47
48 export interface FamilyGuidance {
49 purpose: LocalizedText;
50 responsibility: LocalizedText;
51 example: string;
52 }
53
54 /** Teach composition without making the gallery's example state an application authority. */
55 export const FAMILY_GUIDANCE: Record<string, FamilyGuidance> = {
56 studio: {
57 purpose: { en: "See how the native composer, conversation and workbar fit together.", zh: "了解原生输入框、对话与工作栏如何组合。" },
58 responsibility: { en: "Adapt the layout to your data; your app handles input and session state.", zh: "根据自己的数据调整布局;输入与会话状态由应用处理。" },
59 example: "examples/showcase.rs",
60 },
61 "native-views": {
62 purpose: { en: "Build session lists, settings and pickers from Codewhale's terminal parts.", zh: "用 Codewhale 终端组件构建会话列表、设置与选择器。" },
63 responsibility: { en: "Supply rows and selection, then handle the resulting actions in your app.", zh: "提供行与选择状态,并在应用中处理相应操作。" },
64 example: "examples/gallery.rs",
65 },
66 "native-chrome": {
67 purpose: { en: "Compose the native input area, permission words, workflow progress and metrics.", zh: "组合原生输入区域、权限文字、工作流进度与指标。" },
68 responsibility: { en: "Own the editable draft, cursor and reported state; painting does not submit a message.", zh: "管理可编辑草稿、光标与状态;渲染不会发送消息。" },
69 example: "examples/starter.rs",
70 },
71 workbar: {
72 purpose: { en: "Show Tasks, Fleet, Jobs, Files, Notes, Context, Git or Cost in a native workbar.", zh: "在原生工作栏中展示任务、Fleet、作业、文件、笔记、上下文、Git 或费用。" },
73 responsibility: { en: "Supply stable row IDs and panel facts, and connect selection to your own actions.", zh: "提供稳定的行 ID 与面板数据,并将选择连接到自己的操作。" },
74 example: "examples/starter.rs",
75 },
76 scenes: {
77 purpose: { en: "Explore optional workspace, review, fleet and habitat compositions.", zh: "探索可选的工作区、审查、Fleet 与海洋生境组合。" },
78 responsibility: { en: "Reuse the parts you need; these scenes contain example data rather than live sessions.", zh: "复用所需组件;这些场景包含示例数据,而非实时会话。" },
79 example: "examples/gallery.rs",
80 },
81 input: {
82 purpose: { en: "Edit text, validate fields and choose from searchable lists.", zh: "编辑文本、验证字段,并从可搜索列表中选择。" },
83 responsibility: { en: "Keep input state and validation in your app, and handle submitted or cancelled outcomes.", zh: "在应用中管理输入状态与验证,并处理提交或取消结果。" },
84 example: "examples/starter.rs",
85 },
86 chrome: {
87 purpose: { en: "Add headings, tabs, toggles, settings details and keyboard hints.", zh: "添加标题、标签页、开关、设置详情与键盘提示。" },
88 responsibility: { en: "Update selected values and dispatch keys; the controls display your current state.", zh: "更新选定值并分发按键;控件展示应用当前的状态。" },
89 example: "examples/gallery.rs",
90 },
91 transcript: {
92 purpose: { en: "Display structured prose, code, links, attachments and queued instructions.", zh: "展示结构化正文、代码、链接、附件与排队指令。" },
93 responsibility: { en: "Supply parsed content and queue state; your app owns scrolling, links and clipboard actions.", zh: "提供解析后的内容与队列状态;应用负责滚动、链接与剪贴板操作。" },
94 example: "examples/gallery.rs",
95 },
96 components: {
97 purpose: { en: "Combine messages, tool and agent cards, attention queues and workspace framing.", zh: "组合消息、工具与智能体卡片、待处理队列及工作区框架。" },
98 responsibility: { en: "Supply conversation and agent facts, and keep execution and priority decisions in your app.", zh: "提供对话与智能体数据;执行与优先级决策由应用负责。" },
99 example: "examples/gallery.rs",
100 },
101 display: {
102 purpose: { en: "Present diffs, approvals, artifacts, review decisions and run results.", zh: "展示差异、审批、产物、审查决定与运行结果。" },
103 responsibility: { en: "Supply the changes and results, and handle approval choices in your app.", zh: "提供变更与结果,并在应用中处理审批选择。" },
104 example: "examples/gallery.rs",
105 },
106 "tui-palettes": {
107 purpose: { en: "Compare the sixteen native Codewhale themes and their backgrounds and semantic inks.", zh: "比较十六种原生 Codewhale 主题及其背景与语义颜色。" },
108 responsibility: { en: "Choose a palette and terminal capabilities; use semantic roles for readable states.", zh: "选择调色板与终端能力,并用语义角色保证状态易读。" },
109 example: "examples/showcase.rs",
110 },
111 water: {
112 purpose: { en: "Apply native ocean depth or an optional ombré treatment to rendered components.", zh: "为已渲染组件添加原生海洋深度或可选渐变效果。" },
113 responsibility: { en: "Paint components first, then apply the background pass with your viewport and motion policy.", zh: "先渲染组件,再根据视口与动画策略应用背景效果。" },
114 example: "examples/starter.rs",
115 },
116 foundation: {
117 purpose: { en: "Use shared surfaces, depth, dividers, icons, status marks and key hints.", zh: "使用共享表面、层次、分隔线、图标、状态标记与按键提示。" },
118 responsibility: { en: "Choose meaningful labels and layout bounds, and retain words alongside status color.", zh: "选择清晰标签与布局边界,并在状态颜色旁保留文字。" },
119 example: "examples/gallery.rs",
120 },
121 motion: {
122 purpose: { en: "Show running work, verification, notifications and calm state transitions.", zh: "展示进行中的工作、验证、通知与平静的状态过渡。" },
123 responsibility: { en: "Supply elapsed time and completion status; choose Full, Reduced or Still motion and schedule redraws.", zh: "提供经过时间与完成状态;选择 Full、Reduced 或 Still 动画并安排重绘。" },
124 example: "examples/motion.rs",
125 },
126 habitat: {
127 purpose: { en: "Render fish schools, jellyfish and bubbles together or individually.", zh: "组合或单独渲染鱼群、水母与气泡。" },
128 responsibility: { en: "Supply the clock, density and motion preference; the creatures render into your chosen area.", zh: "提供时钟、密度与动画偏好;海洋生物渲染到选定区域。" },
129 example: "examples/habitat.rs",
130 },
131 whales: {
132 purpose: { en: "Give session state a whale: resting, busy, needing attention, done or with a pod.", zh: "用鲸鱼表现会话状态:休息、忙碌、需要关注、完成或带有鲸群。" },
133 responsibility: { en: "Map your app's state to WhaleState; use the compact or words-only fallback when space is tight.", zh: "将应用状态映射到 WhaleState;空间不足时使用紧凑或纯文字样式。" },
134 example: "examples/showcase.rs",
135 },
136 "whale-actions": {
137 purpose: { en: "Explore the whale's full action vocabulary and related animated performances.", zh: "探索鲸鱼的完整动作语言与相关动画表现。" },
138 responsibility: { en: "For animation, keep a whale_motion Stage and supply inputs and time from your host.", zh: "播放动画时保留 whale_motion Stage,并由应用提供输入与时间。" },
139 example: "examples/showcase.rs",
140 },
141 };
142
143 export function normalizeSearch(value: string): string {
144 return value.normalize("NFD").replace(/\p{M}/gu, "").toLocaleLowerCase();
145 }
146
147 /** Name-scoped aliases avoid the repeated family descriptions matching unrelated widgets. */
148 const ALIASES: [RegExp, string][] = [
149 [/^(tui-theme-|view-theme$)/, "themes palette colors colours 主题 调色板"],
150 [/^(atmosphere-|ocean-)/, "ombre ombres gradient background underwater 渐变 背景 海洋"],
151 [/^(spinner$|verification-pending$|motion-working$)/, "loading busy waiting 加载 等待"],
152 [/^verification-/, "verification verified checks 验证 检查"],
153 [/^(workflow-progress-|count-bars$)/, "progress completion 进度 完成"],
154 [/^workflow-tree/, "tasks hierarchy tree 任务 层级"],
155 [/^(text-input-|native-composer|composer$)/, "input edit editable typing 输入 编辑"],
156 [/^form$/, "forms fields validation 表单 字段 验证"],
157 [/^(picker-|mode-picker$|status-picker$|view-commands$|view-models)/, "search filter select choices 搜索 筛选 选择"],
158 [/^native-composer-rich-search$/, "search find 搜索 查找"],
159 [/^list/, "selection rows scroll 选择 列表 滚动"],
160 [/^fish-school$/, "fish fishes swimming 鱼 鱼群"],
161 [/^jellyfish$/, "jelly jellyfish 水母"],
162 [/^bubble-field$/, "bubbles 气泡"],
163 [/^habitat-(scene|ascii|reduced)$/, "fish jellyfish bubbles aquarium 鱼 水母 气泡"],
164 [/^whale-/, "whales mascot character 鲸鱼 角色"],
165 [/^whale-needs$/, "attention waiting needs input 关注 等待 输入"],
166 [/^workbar-/, "dock panel 工作栏 面板"],
167 [/^(transcript-|message$)/, "conversation messages prose 对话 消息"],
168 [/^pending-/, "queue queued attachments steering 队列 附件"],
169 [/^diff/, "changes patch review 差异 变更 审查"],
170 [/^approval-/, "permission decision approve 审批 权限"],
171 [/^receipt-/, "results cost usage 结果 费用 用量"],
172 [/^toasts/, "notifications feedback 通知 反馈"],
173 ];
174
175 export function discoveryText(entry: CatalogueEntry): string {
176 const aliases = ALIASES.filter(([pattern]) => pattern.test(entry.name)).map(([, text]) => text);
177 return normalizeSearch([entry.name, entry.title, ...displayApi(entry), ...aliases].join(" "));
178 }
179
180 export function getGuidance(entry: CatalogueEntry, locale: string): {
181 title: string;
182 description: string;
183 hostNote: string;
184 example: string;
185 recipeId?: string;
186 guideAnchor?: string;
187 } {
188 const guidance = FAMILY_GUIDANCE[entry.family];
189 let recipeId: string | undefined;
190 if (entry.family === "native-chrome" && entry.api.includes("NativeComposer")) recipeId = "composer";
191 if (entry.family === "workbar" && entry.api.includes("Workbar")) recipeId = "workbar";
192 if (entry.family === "water" && entry.api.includes("OceanColumn")) recipeId = "ocean";
193 if (entry.family === "whales" && entry.api.includes("Whale")) recipeId = "whale";
194 if (entry.name === "whale-actions") recipeId = "animated-whale";
195 return {
196 title: entry.title,
197 description: guidance ? pickText(guidance.purpose, locale) : entry.description,
198 hostNote: guidance ? pickText(guidance.responsibility, locale) : pickText({
199 en: "Your app supplies state and handles input; components render into your terminal.",
200 zh: "应用提供状态并处理输入;组件渲染到你的终端。",
201 }, locale),
202 example: guidance?.example ?? "examples/gallery.rs",
203 guideAnchor: ["motion", "habitat", "whales", "whale-actions"].includes(entry.family)
204 ? "bring-the-whale-and-water-to-life"
205 : ["tui-palettes", "water", "foundation"].includes(entry.family)
206 ? "use-the-native-colors-and-background"
207 : ["native-chrome", "input", "chrome", "workbar"].includes(entry.family)
208 ? "connect-input-to-your-state" : "choose-the-parts-you-need",
209 ...(recipeId ? { recipeId } : {}),
210 };
211 }
212
213 export function filterByTask(entries: readonly CatalogueEntry[], taskId: string): CatalogueEntry[] {
214 const task = TASKS.find((candidate) => candidate.id === taskId);
215 return task ? entries.filter((entry) => task.families.includes(entry.family)) : [...entries];
216 }
217
218 /**
219 * The exporter reads API symbols from gallery fixture code, where the studio
220 * sections share one private frame: every showcase entry reports only the
221 * whale-motion types that happen to appear bare. Curate what each composed
222 * scene actually presents (verified against src/gallery/showcase.rs).
223 */
224 const ENTRY_API: Record<string, string[]> = {
225 "showcase-work": ["TerminalShell", "Message", "NativeComposer", "PostureBar", "MetricsLine", "Workbar", "OceanColumn"],
226 "showcase-decision": ["TerminalShell", "ApprovalCard", "Diff", "Form", "Toggle"],
227 "showcase-color": ["TuiPalette", "Ombre", "Segmented", "HorizonRule"],
228 "showcase-life": ["Whale", "WhaleState", "Habitat", "Picker"],
229 "showcase-narrow": ["TerminalShell", "Message", "NativeComposer", "PostureBar", "MetricsLine", "Workbar", "OceanColumn"],
230 };
231
232 export function displayApi(entry: CatalogueEntry): string[] {
233 return ENTRY_API[entry.name] ?? entry.api;
234 }
235
235 lines TYPESCRIPT