paseo-kanban 0.1.0 → 0.1.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 breathi3552
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 breathi3552
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -21,23 +21,25 @@ A lightweight, responsive Kanban board plugin for [Paseo](https://paseo.sh) work
21
21
 
22
22
  ## Requirements
23
23
 
24
- - **Paseo**: `>= 0.8.0`
25
- - **Node.js**: `>= 20.0.0` (recommended for development)
24
+ - **Paseo Host Runtime Commitment**: `>= 0.8.0` (declared in `paseo-plugin.json`). As a Paseo workspace plugin, production runtime execution is managed and hosted by the Paseo application environment. No consumer-side Node version restriction is imposed in `package.json`.
25
+ - **Development & CI Baseline**: Node.js 24 (pinned in `.nvmrc` and `.node-version`). Node 24 is required for local contributors and CI automation workflows running quality checks (`npm run check`), type stripping (`--experimental-strip-types`), and native test suites (`node --test`).
26
26
 
27
27
  ---
28
28
 
29
- ## Installation & Usage
29
+ ## Installation
30
30
 
31
31
  ### Quick Install (Recommended)
32
32
 
33
33
  No manual build or git cloning needed. You can install directly via npm or GitHub:
34
34
 
35
35
  **Via NPM (Release package):**
36
+
36
37
  ```bash
37
38
  paseo plugin install npm:paseo-kanban
38
39
  ```
39
40
 
40
41
  **Via GitHub:**
42
+
41
43
  ```bash
42
44
  paseo plugin add breathi3552/paseo-kanban
43
45
  ```
@@ -69,6 +71,7 @@ npm install
69
71
  # 2. Link this directory into your local Paseo daemon
70
72
  paseo plugin link .
71
73
  ```
74
+
72
75
  ---
73
76
 
74
77
  ## Development & Testing
@@ -77,23 +80,43 @@ paseo plugin link .
77
80
 
78
81
  ```bash
79
82
  npm install
83
+ # or clean install:
84
+ npm ci
80
85
  ```
81
86
 
82
- ### Run Tests
87
+ ### Unified Quality Guardrails (Check)
83
88
 
84
- Runs unit tests for board domain logic, subtask mutations, and drag hit-testing:
89
+ Runs typecheck, automated test suites, linting, format verification, and release package checks in sequence:
85
90
 
86
91
  ```bash
87
- npm test
92
+ npm run check
88
93
  ```
89
94
 
90
- ### Type Checking
91
-
92
- Validates TypeScript types across client and server entry points:
93
-
94
- ```bash
95
- npm run typecheck
96
- ```
95
+ ### Individual Quality Tasks
96
+
97
+ - **Package Verification**: Validates release package contents, entrypoints, and TypeScript AST relative imports:
98
+ ```bash
99
+ npm run check:package
100
+ ```
101
+ - **Run Tests**: Cross-platform auto-discovery of all `test/*.test.mjs` suites:
102
+ ```bash
103
+ npm test
104
+ ```
105
+ - **Type Checking**: Validates TypeScript types across client and server entry points:
106
+ ```bash
107
+ npm run typecheck
108
+ ```
109
+ - **Linting**: Static code analysis with ESLint:
110
+ ```bash
111
+ npm run lint
112
+ ```
113
+ - **Formatting**: Check or format code style with Prettier:
114
+ ```bash
115
+ npm run format:check
116
+ npm run format
117
+ ```
118
+
119
+ Maintainers can publish a new GitHub Release and npm package in one run of the **Release to GitHub and npm** GitHub Action: select `patch`, `minor`, or `major`. Configure `NPM_TOKEN` and repository write permissions first. For setup, recovery, and manual host acceptance, see the [Development & Acceptance Guide](./docs/development.md#24-一键发布github-actions).
97
120
 
98
121
  ---
99
122
 
package/README.zh-CN.md CHANGED
@@ -21,23 +21,25 @@
21
21
 
22
22
  ## 环境要求
23
23
 
24
- - **Paseo**:`>= 0.8.0`
25
- - **Node.js**:`>= 20.0.0`(开发环境推荐)
24
+ - **Paseo 宿主版本承诺**:`>= 0.8.0`(声明于 `paseo-plugin.json`)。作为 Paseo 工作区插件,插件的生产运行时环境由 Paseo 宿主应用提供并托管执行,不在 `package.json` 对消费端强加额外的 `engines.node` 限制。
25
+ - **开发与 CI 环境基线**:Node.js 24(显式声明于 `.nvmrc` 与 `.node-version`)。本地参与开发、运行质量检查护栏(`npm run check`)、代码格式化、类型检查与自动化测试必须基于 Node 24,依赖其原生的 `--experimental-strip-types` 类型擦除与原生测试运行器(`node --test`)。
26
26
 
27
27
  ---
28
28
 
29
- ## 安装使用
29
+ ## 安装
30
30
 
31
31
  ### 快速安装(推荐)
32
32
 
33
33
  无需克隆源码或手动编译,推荐直接使用 Paseo CLI 安装:
34
34
 
35
35
  **方式一:通过 NPM 官方包安装(推荐)**
36
+
36
37
  ```bash
37
38
  paseo plugin install npm:paseo-kanban
38
39
  ```
39
40
 
40
41
  **方式二:直接通过 GitHub 仓库安装**
42
+
41
43
  ```bash
42
44
  paseo plugin add breathi3552/paseo-kanban
43
45
  ```
@@ -69,6 +71,7 @@ npm install
69
71
  # 2. 将当前开发目录软链接至本地 Paseo 守护进程
70
72
  paseo plugin link .
71
73
  ```
74
+
72
75
  ---
73
76
 
74
77
  ## 开发与测试
@@ -77,23 +80,45 @@ paseo plugin link .
77
80
 
78
81
  ```bash
79
82
  npm install
83
+ # 或干净安装验证:
84
+ npm ci
80
85
  ```
81
86
 
82
- ### 运行单元测试
87
+ ### 统一质量护栏检查
83
88
 
84
- 执行看板状态模型、并发校验、子步骤更新以及拖拽命中测试的自动化用例:
89
+ 按顺序执行类型检查、自动化测试套件、静态代码检查、代码格式验证及发布包完整性检查:
85
90
 
86
91
  ```bash
87
- npm test
92
+ npm run check
88
93
  ```
89
94
 
90
- ### 类型检查
91
-
92
- 校验前端组件与服务端扩展的 TypeScript 类型完整性:
93
-
94
- ```bash
95
- npm run typecheck
96
- ```
95
+ ### 各独立质量命令
96
+
97
+ - **发布包完整性检查**:静态校验发布包产物清单、入口完整性与 TypeScript AST 相对导入闭包:
98
+ ```bash
99
+ npm run check:package
100
+ ```
101
+ - **运行单元测试**:跨平台自动发现并执行所有 `test/*.test.mjs` 测试套件:
102
+ ```bash
103
+ npm test
104
+ ```
105
+ - **类型检查**:校验前端组件与服务端扩展的 TypeScript 类型完整性:
106
+ ```bash
107
+ npm run typecheck
108
+ ```
109
+ - **代码规范检查 (Lint)**:基于 ESLint 开展轻量静态检查:
110
+ ```bash
111
+ npm run lint
112
+ ```
113
+ - **代码格式检查与格式化 (Format)**:基于 Prettier 校验或自动格式化:
114
+ ```bash
115
+ npm run format:check
116
+ npm run format
117
+ ```
118
+
119
+ 维护者可在 GitHub Actions 的 **Release to GitHub and npm** 工作流中选择 `patch` / `minor` / `major` 一键发布 GitHub Release 与 npm 包;首次使用需配置 `NPM_TOKEN` 和仓库写入权限。操作及失败重试说明见 [开发与宿主验收指南](./docs/development.md#24-一键发布github-actions)。
120
+
121
+ 详细开发贡献流程、发布前检查机制与真实宿主环境手工验收指南,请参阅 [开发与宿主验收指南](./docs/development.md)。
97
122
 
98
123
  ---
99
124
 
package/client/i18n.tsx CHANGED
@@ -1,9 +1,11 @@
1
- import * as React from "react";
2
1
  import { useState, useEffect, useMemo } from "react";
3
2
 
4
3
  export type SupportedLanguage = "zh" | "en";
5
4
 
6
- export type TranslationParams = Record<string, string | number | boolean | null | undefined>;
5
+ export type TranslationParams = Record<
6
+ string,
7
+ string | number | boolean | null | undefined
8
+ >;
7
9
 
8
10
  export const translations = {
9
11
  zh: {
@@ -16,7 +18,8 @@ export const translations = {
16
18
  "kanban.refresh": "刷新最新数据",
17
19
  "kanban.loadError": "加载看板配置失败:{error}",
18
20
  "kanban.formatError": "看板配置格式异常:{error}",
19
- "kanban.formatErrorHint": "数据未被悄悄覆盖。若需使用默认结构可重置,或重试加载。",
21
+ "kanban.formatErrorHint":
22
+ "数据未被悄悄覆盖。若需使用默认结构可重置,或重试加载。",
20
23
  "kanban.retryLoad": "重试加载",
21
24
  "kanban.resetDefault": "重置为默认结构",
22
25
  "kanban.saveFailed": "保存失败:{error}",
@@ -35,7 +38,8 @@ export const translations = {
35
38
  "laneModal.nameLabel": "泳道名称 *",
36
39
  "laneModal.namePlaceholder": "例如:待测试、发布中...",
37
40
  "laneModal.cannotDeleteLast": "看板必须至少保留一条泳道,无法删除此泳道。",
38
- "laneModal.cannotDeleteHasTasks": "此泳道当前包含 {count} 个任务,需清空或移走任务后方可删除。",
41
+ "laneModal.cannotDeleteHasTasks":
42
+ "此泳道当前包含 {count} 个任务,需清空或移走任务后方可删除。",
39
43
  "laneModal.deleteLane": "删除此泳道",
40
44
  "laneModal.confirmDeletePrompt": "确认删除?",
41
45
  "laneModal.confirmDelete": "确认删除",
@@ -74,7 +78,8 @@ export const translations = {
74
78
  "kanban.refresh": "Refresh latest data",
75
79
  "kanban.loadError": "Failed to load kanban settings: {error}",
76
80
  "kanban.formatError": "Kanban settings format error: {error}",
77
- "kanban.formatErrorHint": "Data was not silently overwritten. Reset to default structure if needed, or retry loading.",
81
+ "kanban.formatErrorHint":
82
+ "Data was not silently overwritten. Reset to default structure if needed, or retry loading.",
78
83
  "kanban.retryLoad": "Retry Loading",
79
84
  "kanban.resetDefault": "Reset to Default",
80
85
  "kanban.saveFailed": "Save failed: {error}",
@@ -85,15 +90,18 @@ export const translations = {
85
90
  "kanban.projectsError": "(Project list update restricted)",
86
91
  "lane.add": "+ Add",
87
92
  "lane.manage": "Manage",
88
- "lane.emptyHint": "No cards yet. Click '+' in the top right or drag cards here.",
93
+ "lane.emptyHint":
94
+ "No cards yet. Click '+' in the top right or drag cards here.",
89
95
  "card.dragHandle": "Drag handle",
90
96
  "card.subtasksCount": "Subtasks {completed}/{total}",
91
97
  "laneModal.titleEdit": "Manage Lane",
92
98
  "laneModal.titleCreate": "Add Lane",
93
99
  "laneModal.nameLabel": "Lane Name *",
94
100
  "laneModal.namePlaceholder": "e.g., Testing, In Review...",
95
- "laneModal.cannotDeleteLast": "A kanban board must retain at least one lane. Cannot delete this lane.",
96
- "laneModal.cannotDeleteHasTasks": "This lane currently contains {count} tasks. Clear or move tasks before deleting.",
101
+ "laneModal.cannotDeleteLast":
102
+ "A kanban board must retain at least one lane. Cannot delete this lane.",
103
+ "laneModal.cannotDeleteHasTasks":
104
+ "This lane currently contains {count} tasks. Clear or move tasks before deleting.",
97
105
  "laneModal.deleteLane": "Delete Lane",
98
106
  "laneModal.confirmDeletePrompt": "Confirm delete?",
99
107
  "laneModal.confirmDelete": "Confirm Delete",
@@ -166,7 +174,10 @@ interface EnvironmentGlobals {
166
174
  language?: string;
167
175
  };
168
176
  window?: {
169
- addEventListener(event: string, listener: (event: { key?: string }) => void): void;
177
+ addEventListener(
178
+ event: string,
179
+ listener: (event: { key?: string }) => void,
180
+ ): void;
170
181
  };
171
182
  i18n?: {
172
183
  language?: string;
@@ -175,7 +186,9 @@ interface EnvironmentGlobals {
175
186
 
176
187
  const env = globalThis as typeof globalThis & EnvironmentGlobals;
177
188
 
178
- export function detectPaseoLanguage(hostProps?: DetectLanguageOptions): SupportedLanguage {
189
+ export function detectPaseoLanguage(
190
+ hostProps?: DetectLanguageOptions,
191
+ ): SupportedLanguage {
179
192
  if (languageOverride) {
180
193
  return languageOverride;
181
194
  }
@@ -254,14 +267,18 @@ function ensureGlobalListeners() {
254
267
  });
255
268
  }
256
269
 
257
- export function getLanguage(hostProps?: DetectLanguageOptions): SupportedLanguage {
270
+ export function getLanguage(
271
+ hostProps?: DetectLanguageOptions,
272
+ ): SupportedLanguage {
258
273
  if (hostProps) {
259
274
  return detectPaseoLanguage(hostProps);
260
275
  }
261
276
  return currentLanguage;
262
277
  }
263
278
 
264
- export function subscribeLanguage(listener: (lang: SupportedLanguage) => void): () => void {
279
+ export function subscribeLanguage(
280
+ listener: (lang: SupportedLanguage) => void,
281
+ ): () => void {
265
282
  listeners.add(listener);
266
283
  ensureGlobalListeners();
267
284
  return () => {
@@ -269,7 +286,10 @@ export function subscribeLanguage(listener: (lang: SupportedLanguage) => void):
269
286
  };
270
287
  }
271
288
 
272
- export function formatString(template: string, params?: TranslationParams): string {
289
+ export function formatString(
290
+ template: string,
291
+ params?: TranslationParams,
292
+ ): string {
273
293
  if (!params) return template;
274
294
  return template.replace(/\{(\w+)\}/g, (match, key) => {
275
295
  return params[key] != null ? String(params[key]) : match;
@@ -279,7 +299,7 @@ export function formatString(template: string, params?: TranslationParams): stri
279
299
  export function t(
280
300
  key: TranslationKey,
281
301
  langOrParams?: SupportedLanguage | TranslationParams,
282
- params?: TranslationParams
302
+ params?: TranslationParams,
283
303
  ): string {
284
304
  let lang: SupportedLanguage;
285
305
  let finalParams: TranslationParams | undefined;
@@ -303,22 +323,28 @@ export interface I18nContextValue {
303
323
  }
304
324
 
305
325
  export function useI18n(hostProps?: DetectLanguageOptions): I18nContextValue {
306
- const [lang, setLang] = useState<SupportedLanguage>(() => detectPaseoLanguage(hostProps));
326
+ const hostLocale =
327
+ hostProps?.locale ??
328
+ hostProps?.language ??
329
+ hostProps?.host?.locale ??
330
+ hostProps?.host?.language;
331
+ const [lang, setLang] = useState<SupportedLanguage>(() =>
332
+ detectPaseoLanguage(hostProps),
333
+ );
307
334
 
308
335
  useEffect(() => {
309
336
  ensureGlobalListeners();
310
337
  const immediate = detectPaseoLanguage(hostProps);
311
- if (immediate !== lang) {
312
- setLang(immediate);
313
- }
338
+ setLang((prev) => (immediate !== prev ? immediate : prev));
314
339
  return subscribeLanguage((newLang) => {
315
340
  setLang(newLang);
316
341
  });
317
- }, [hostProps?.host?.language, hostProps?.locale, hostProps?.language]);
342
+ }, [hostLocale, hostProps]);
318
343
 
319
344
  const tBound = useMemo(
320
- () => (key: TranslationKey, params?: TranslationParams) => t(key, lang, params),
321
- [lang]
345
+ () => (key: TranslationKey, params?: TranslationParams) =>
346
+ t(key, lang, params),
347
+ [lang],
322
348
  );
323
349
 
324
350
  return {
@@ -9,12 +9,8 @@ import {
9
9
  } from "react-native";
10
10
  import { useSettings } from "@getpaseo/plugin/client";
11
11
  import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
12
- import {
13
- kanbanSettings,
14
- filterTasksByProject,
15
- type KanbanTask,
16
- } from "../shared/kanban";
17
- import { useProjects, getProjectDisplayName } from "./use-projects";
12
+ import { kanbanSettings, filterTasksByProject } from "../shared/kanban";
13
+ import { useProjects } from "./use-projects";
18
14
  import { TaskModal } from "./task-modal";
19
15
  import { LaneModal } from "./lane-modal";
20
16
  import { useKanbanDrag } from "./kanban-drag";
@@ -33,11 +29,17 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
33
29
  const { theme, layout } = props;
34
30
  const { t, language } = useI18n(props);
35
31
  const settings = useSettings(kanbanSettings);
36
- const { projects, resolveProjectName, isError: projectsError } = useProjects();
32
+ const {
33
+ projects,
34
+ resolveProjectName,
35
+ isError: projectsError,
36
+ } = useProjects();
37
37
 
38
38
  const [selectedProjectId, setSelectedProjectId] = useState<string>("all");
39
- const [activeTaskSession, setActiveTaskSession] = useState<TaskEditSession | null>(null);
40
- const [activeLaneSession, setActiveLaneSession] = useState<LaneEditSession | null>(null);
39
+ const [activeTaskSession, setActiveTaskSession] =
40
+ useState<TaskEditSession | null>(null);
41
+ const [activeLaneSession, setActiveLaneSession] =
42
+ useState<LaneEditSession | null>(null);
41
43
  const [reorderError, setReorderError] = useState<string | null>(null);
42
44
 
43
45
  const isReady = settings.status === "ready";
@@ -53,7 +55,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
53
55
  const handleReorderTask = async (
54
56
  taskId: string,
55
57
  targetLaneId: string,
56
- targetIndex: number
58
+ targetIndex: number,
57
59
  ) => {
58
60
  if (!board) return;
59
61
  const result = await executeTaskReorder({
@@ -102,7 +104,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
102
104
  ? null
103
105
  : selectedProjectId,
104
106
  language,
105
- })
107
+ }),
106
108
  );
107
109
  };
108
110
 
@@ -116,7 +118,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
116
118
  baseRevision: revision,
117
119
  save: (b, r) => settings.save(b, r),
118
120
  language,
119
- })
121
+ }),
120
122
  );
121
123
  };
122
124
 
@@ -129,7 +131,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
129
131
  baseRevision: revision,
130
132
  save: (b, r) => settings.save(b, r),
131
133
  language,
132
- })
134
+ }),
133
135
  );
134
136
  };
135
137
 
@@ -143,7 +145,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
143
145
  baseRevision: revision,
144
146
  save: (b, r) => settings.save(b, r),
145
147
  language,
146
- })
148
+ }),
147
149
  );
148
150
  };
149
151
 
@@ -294,7 +296,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
294
296
  textAlign: "center",
295
297
  },
296
298
  }),
297
- [theme, layout.compact]
299
+ [theme, layout.compact],
298
300
  );
299
301
 
300
302
  if (settings.status === "loading") {
@@ -312,8 +314,13 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
312
314
  <Text style={[styles.centerText, { color: theme.colors.statusDanger }]}>
313
315
  {t("kanban.loadError", { error: settings.error })}
314
316
  </Text>
315
- <Pressable onPress={() => settings.reload()} style={styles.secondaryButton}>
316
- <Text style={styles.secondaryButtonText}>{t("kanban.retryLoad")}</Text>
317
+ <Pressable
318
+ onPress={() => settings.reload()}
319
+ style={styles.secondaryButton}
320
+ >
321
+ <Text style={styles.secondaryButtonText}>
322
+ {t("kanban.retryLoad")}
323
+ </Text>
317
324
  </Pressable>
318
325
  </View>
319
326
  );
@@ -322,18 +329,38 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
322
329
  if (settings.status === "invalid") {
323
330
  return (
324
331
  <View style={[styles.container, styles.centerBox]}>
325
- <Text style={[styles.centerText, { color: theme.colors.statusWarning ?? theme.colors.statusDanger }]}>
332
+ <Text
333
+ style={[
334
+ styles.centerText,
335
+ { color: theme.colors.statusWarning ?? theme.colors.statusDanger },
336
+ ]}
337
+ >
326
338
  {t("kanban.formatError", { error: settings.error })}
327
339
  </Text>
328
- <Text style={[styles.centerText, { color: theme.colors.foregroundMuted, fontSize: 12 }]}>
340
+ <Text
341
+ style={[
342
+ styles.centerText,
343
+ { color: theme.colors.foregroundMuted, fontSize: 12 },
344
+ ]}
345
+ >
329
346
  {t("kanban.formatErrorHint")}
330
347
  </Text>
331
348
  <View style={{ flexDirection: "row", gap: 10 }}>
332
- <Pressable onPress={() => settings.reload()} style={styles.secondaryButton}>
333
- <Text style={styles.secondaryButtonText}>{t("kanban.retryLoad")}</Text>
349
+ <Pressable
350
+ onPress={() => settings.reload()}
351
+ style={styles.secondaryButton}
352
+ >
353
+ <Text style={styles.secondaryButtonText}>
354
+ {t("kanban.retryLoad")}
355
+ </Text>
334
356
  </Pressable>
335
- <Pressable onPress={() => settings.reset()} style={styles.primaryButton}>
336
- <Text style={styles.primaryButtonText}>{t("kanban.resetDefault")}</Text>
357
+ <Pressable
358
+ onPress={() => settings.reset()}
359
+ style={styles.primaryButton}
360
+ >
361
+ <Text style={styles.primaryButtonText}>
362
+ {t("kanban.resetDefault")}
363
+ </Text>
337
364
  </Pressable>
338
365
  </View>
339
366
  </View>
@@ -349,35 +376,43 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
349
376
  <View style={styles.headerTopRow}>
350
377
  <View style={styles.titleRow}>
351
378
  <Text style={styles.titleText}>{t("kanban.title")}</Text>
352
- <View style={{
353
- backgroundColor: theme.colors.surface2,
354
- paddingHorizontal: 6,
355
- paddingVertical: 2,
356
- borderRadius: 10,
357
- }}>
358
- <Text style={{
359
- fontSize: 11,
360
- color: theme.colors.foregroundMuted,
361
- fontWeight: "600",
362
- }}>
363
- {t("kanban.stats", { tasks: board.tasks.length, lanes: board.lanes.length })}
379
+ <View
380
+ style={{
381
+ backgroundColor: theme.colors.surface2,
382
+ paddingHorizontal: 6,
383
+ paddingVertical: 2,
384
+ borderRadius: 10,
385
+ }}
386
+ >
387
+ <Text
388
+ style={{
389
+ fontSize: 11,
390
+ color: theme.colors.foregroundMuted,
391
+ fontWeight: "600",
392
+ }}
393
+ >
394
+ {t("kanban.stats", {
395
+ tasks: board.tasks.length,
396
+ lanes: board.lanes.length,
397
+ })}
364
398
  </Text>
365
399
  </View>
366
400
  </View>
367
401
 
368
402
  <View style={styles.headerActions}>
369
- <Pressable
370
- onPress={openNewLane}
371
- style={styles.secondaryButton}
372
- >
373
- <Text style={styles.secondaryButtonText}>{t("kanban.addLane")}</Text>
403
+ <Pressable onPress={openNewLane} style={styles.secondaryButton}>
404
+ <Text style={styles.secondaryButtonText}>
405
+ {t("kanban.addLane")}
406
+ </Text>
374
407
  </Pressable>
375
408
 
376
409
  <Pressable
377
410
  onPress={() => openNewTask()}
378
411
  style={styles.primaryButton}
379
412
  >
380
- <Text style={styles.primaryButtonText}>{t("kanban.newTask")}</Text>
413
+ <Text style={styles.primaryButtonText}>
414
+ {t("kanban.newTask")}
415
+ </Text>
381
416
  </Pressable>
382
417
  </View>
383
418
  </View>
@@ -419,7 +454,8 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
419
454
  <Text
420
455
  style={[
421
456
  styles.filterChipText,
422
- selectedProjectId === "unassigned" && styles.filterChipTextSelected,
457
+ selectedProjectId === "unassigned" &&
458
+ styles.filterChipTextSelected,
423
459
  ]}
424
460
  >
425
461
  {t("kanban.filterUnassigned")}
@@ -453,7 +489,13 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
453
489
  })}
454
490
 
455
491
  {projectsError && (
456
- <Text style={{ fontSize: 11, color: theme.colors.statusWarning ?? theme.colors.foregroundMuted }}>
492
+ <Text
493
+ style={{
494
+ fontSize: 11,
495
+ color:
496
+ theme.colors.statusWarning ?? theme.colors.foregroundMuted,
497
+ }}
498
+ >
457
499
  {t("kanban.projectsError")}
458
500
  </Text>
459
501
  )}
@@ -476,7 +518,9 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
476
518
  }}
477
519
  style={styles.conflictAction}
478
520
  >
479
- <Text style={{ fontSize: 12, color: theme.colors.foreground }}>{t("kanban.refresh")}</Text>
521
+ <Text style={{ fontSize: 12, color: theme.colors.foreground }}>
522
+ {t("kanban.refresh")}
523
+ </Text>
480
524
  </Pressable>
481
525
  </View>
482
526
  )}
@@ -489,7 +533,9 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
489
533
  style={styles.lanesContainer}
490
534
  contentContainerStyle={styles.lanesContent}
491
535
  onLayout={(e) => drag.handleContainerLayout(e.nativeEvent.layout)}
492
- onScroll={(e) => drag.handleContainerScroll(e.nativeEvent.contentOffset.x)}
536
+ onScroll={(e) =>
537
+ drag.handleContainerScroll(e.nativeEvent.contentOffset.x)
538
+ }
493
539
  scrollEventThrottle={16}
494
540
  >
495
541
  {board.lanes.map((lane) => {
@@ -512,11 +558,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
512
558
  })}
513
559
  </ScrollView>
514
560
 
515
- <KanbanDragOverlay
516
- drag={drag}
517
- theme={theme}
518
- layout={layout}
519
- />
561
+ <KanbanDragOverlay drag={drag} theme={theme} layout={layout} />
520
562
  </View>
521
563
 
522
564
  {/* Modals with Session Baseline Locking */}