paseo-kanban 0.1.1 → 0.1.3

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/OVERVIEW.md ADDED
@@ -0,0 +1,9 @@
1
+ # Kanban
2
+
3
+ Organize tasks in customizable swimlanes, add descriptions and subtasks, and link tasks to Paseo projects. Drag cards to reorder them within a lane or move them between lanes.
4
+
5
+ Open the board from the sidebar or from a workspace's `+` menu. Workspace tabs initially filter to their project's tasks and can sit beside an agent or terminal using Paseo's native split controls. Each open board has its own project filter; all boards on the same host share tasks and lanes.
6
+
7
+ Board data is stored in Paseo's host-scoped plugin settings. Concurrent edits use revision checks so a stale edit cannot overwrite a newer board. If a project's name cannot be read, its stored ID remains available. If workspace project context is unavailable when the board opens, it initially shows all tasks.
8
+
9
+ Requires Paseo 0.8.0 or later. No accounts, tokens, or additional setup are required.
package/README.md CHANGED
@@ -8,11 +8,12 @@ A lightweight, responsive Kanban board plugin for [Paseo](https://paseo.sh) work
8
8
 
9
9
  ## Features
10
10
 
11
- - **Flexible Swimlanes**: Default lanes (`To Plan`, `In Progress`, `Done`) with full support for adding, renaming, and deleting custom lanes.
11
+ - **Flexible Swimlanes**: Three initial lanes that you can rename, with support for adding and deleting custom lanes.
12
12
  - **Rich Task Cards**:
13
- - Task title and multi-line markdown description.
13
+ - Task title and multi-line description.
14
14
  - Subtasks checklist with completion toggles and live progress tracking.
15
15
  - Native Paseo project association.
16
+ - **Workspace Tabs and Splits**: Open the board beside an agent or terminal, initially filtered to the workspace's project. The sidebar remains available.
16
17
  - **Project Filtering**: Instantly filter board cards by linked Paseo project to keep context focused.
17
18
  - **Smooth Drag-and-Drop**: Real-time hit-testing for reordering cards within lanes and moving cards across lanes.
18
19
  - **Versioned State Persistence**: Persists board data through Paseo's settings API with optimistic concurrency control (CAS) to prevent race conditions and accidental overwrites.
@@ -21,16 +22,16 @@ A lightweight, responsive Kanban board plugin for [Paseo](https://paseo.sh) work
21
22
 
22
23
  ## Requirements
23
24
 
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`).
25
+ - **Paseo host**: `>= 0.8.0` (declared in `paseo-plugin.json`). The host runs the plugin.
26
+ - **Development and CI**: Node.js 24 (pinned in `.nvmrc` and `.node-version`) for checks, TypeScript stripping, and native tests.
26
27
 
27
28
  ---
28
29
 
29
- ## Installation & Usage
30
+ ## Installation
30
31
 
31
32
  ### Quick Install (Recommended)
32
33
 
33
- No manual build or git cloning needed. You can install directly via npm or GitHub:
34
+ Install directly from npm or GitHub:
34
35
 
35
36
  **Via NPM (Release package):**
36
37
 
@@ -56,7 +57,12 @@ Verify that the plugin is running:
56
57
  paseo plugin ls
57
58
  ```
58
59
 
59
- Once active, open Paseo. The **Kanban** icon (`PanelsTopLeft`) will appear in the sidebar. Click it to launch the board.
60
+ Once active, open the board from either entry:
61
+
62
+ - **Sidebar → Kanban**: shows all tasks by default.
63
+ - **Workspace → `+` → Kanban**: initially filters to the workspace's project, with other filters available. Use Paseo's native split controls to place it beside an agent or terminal.
64
+
65
+ Both entries share tasks and lanes with full editing support; their filters are independent. Closing and reopening the board or restarting restores the entry's default filter. If workspace project context is unavailable, the board silently shows all tasks and keeps that filter even if context arrives later. Narrow splits retain horizontal lane scrolling.
60
66
 
61
67
  ### Local Development
62
68
 
@@ -66,7 +72,7 @@ To contribute or debug locally:
66
72
  # 1. Clone repository and install dependencies
67
73
  git clone https://github.com/breathi3552/paseo-kanban.git
68
74
  cd paseo-kanban
69
- npm install
75
+ npm ci
70
76
 
71
77
  # 2. Link this directory into your local Paseo daemon
72
78
  paseo plugin link .
@@ -76,56 +82,26 @@ paseo plugin link .
76
82
 
77
83
  ## Development & Testing
78
84
 
79
- ### Install Dependencies
85
+ Install dependencies and run the full quality check:
80
86
 
81
87
  ```bash
82
- npm install
83
- # or clean install:
84
88
  npm ci
89
+ npm run check
85
90
  ```
86
91
 
87
- ### Unified Quality Guardrails (Check)
92
+ Individual commands (`npm test`, `npm run typecheck`, `npm run lint`, `npm run format:check`, `npm run check:package`) are defined in [`package.json`](./package.json). For tag-based publishing, release sources of truth, recovery, and host acceptance, see the [development guide](./docs/development.md#21-标签发布github-actions).
88
93
 
89
- Runs typecheck, automated test suites, linting, format verification, and release package checks in sequence:
94
+ ---
90
95
 
91
- ```bash
92
- npm run check
93
- ```
96
+ ## Releases
94
97
 
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
- For comprehensive contribution guidelines, pre-publish package checks, and manual host acceptance steps, see the [Development & Acceptance Guide](./docs/development.md).
98
+ See the [0.1.3 release notes](./docs/releases/0.1.3.md). Each release's npm package and GitHub Release share the same version tag; GitHub includes the verified npm tarball. `main` may contain changes beyond the latest release.
120
99
 
121
100
  ---
122
101
 
123
102
  ## Architecture & Design
124
103
 
125
- The complete architectural breakdown and interactive visual diagram are archived in the repository:
126
-
127
- - **Interactive Architecture Viewer**: Open [`docs/architecture/paseo-kanban.architecture.html`](./docs/architecture/paseo-kanban.architecture.html) in your browser.
128
- - **Architecture Schema & Model**: [`docs/architecture/paseo-kanban.architecture.json`](./docs/architecture/paseo-kanban.architecture.json).
104
+ Core terminology and design decisions are documented in [`CONTEXT.md`](./CONTEXT.md) and [`docs/adr/`](./docs/adr/).
129
105
 
130
106
  ---
131
107
 
package/README.zh-CN.md CHANGED
@@ -13,24 +13,25 @@
13
13
  - 任务标题与多行描述编辑。
14
14
  - 子步骤清单支持独立增删改、完成状态勾选及实时进度展示。
15
15
  - 支持关联本地已有的 Paseo 项目。
16
+ - **工作区标签页与分屏**:与 Agent、Terminal 并排打开看板,默认筛选工作区所属项目;保留侧边栏入口。
16
17
  - **项目维度筛选**:一键按关联的 Paseo 项目过滤看板卡片,聚焦当前目标。
17
18
  - **流畅拖拽交互**:精准的拖拽命中测试(Hit-Testing),支持同泳道排序与跨泳道状态流转。
18
- - **版本化数据持久化**:基于 Paseo 原生 settings API 存储,内置乐观并发版本控制(CAS),杜绝多会话覆写与状态不一致。
19
+ - **版本化数据持久化**:通过 Paseo settings API 存储看板,使用乐观并发版本控制(CAS)处理多会话写入冲突。
19
20
 
20
21
  ---
21
22
 
22
23
  ## 环境要求
23
24
 
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`)。
25
+ - **Paseo 宿主**:`>= 0.8.0`(声明于 `paseo-plugin.json`),由 Paseo 运行插件。
26
+ - **开发与 CI**:使用 Node.js 24(声明于 `.nvmrc` 与 `.node-version`)执行质量检查、TypeScript 类型擦除和原生测试。
26
27
 
27
28
  ---
28
29
 
29
- ## 安装使用
30
+ ## 安装
30
31
 
31
32
  ### 快速安装(推荐)
32
33
 
33
- 无需克隆源码或手动编译,推荐直接使用 Paseo CLI 安装:
34
+ 通过 Paseo CLI 从 npm 或 GitHub 安装:
34
35
 
35
36
  **方式一:通过 NPM 官方包安装(推荐)**
36
37
 
@@ -56,7 +57,12 @@ paseo plugin add breathi3552/paseo-kanban --ref main
56
57
  paseo plugin ls
57
58
  ```
58
59
 
59
- 状态显示为 `running` 后打开 Paseo,侧边栏将出现 **看板** 入口(图标:`PanelsTopLeft`),点击即可进入看板。
60
+ 状态显示为 `running` 后,可从两个入口打开看板:
61
+
62
+ - **侧边栏 → 看板**:默认展示全部任务。
63
+ - **工作区 → `+` → 看板**:默认筛选工作区所属项目,可切换范围;使用 Paseo 原生分屏操作与 Agent、Terminal 等并排显示。
64
+
65
+ 两个入口共享任务和泳道,保留完整编辑能力,筛选互不影响。关闭重开或重启后恢复入口默认筛选。无法取得工作区所属项目时静默展示全部任务,本次打开期间不因项目上下文恢复而自动切换。窄分屏保持横向滚动浏览泳道。
60
66
 
61
67
  ### 本地开发与调试
62
68
 
@@ -66,7 +72,7 @@ paseo plugin ls
66
72
  # 1. 克隆代码仓库并安装依赖
67
73
  git clone https://github.com/breathi3552/paseo-kanban.git
68
74
  cd paseo-kanban
69
- npm install
75
+ npm ci
70
76
 
71
77
  # 2. 将当前开发目录软链接至本地 Paseo 守护进程
72
78
  paseo plugin link .
@@ -76,56 +82,26 @@ paseo plugin link .
76
82
 
77
83
  ## 开发与测试
78
84
 
79
- ### 安装依赖
85
+ 安装依赖并运行完整检查:
80
86
 
81
87
  ```bash
82
- npm install
83
- # 或干净安装验证:
84
88
  npm ci
89
+ npm run check
85
90
  ```
86
91
 
87
- ### 统一质量护栏检查
92
+ 单项命令(`npm test`、`npm run typecheck`、`npm run lint`、`npm run format:check`、`npm run check:package`)见 [`package.json`](./package.json)。标签发布、事实来源、失败重试与宿主验收见[开发指南](./docs/development.md#21-标签发布github-actions)。
88
93
 
89
- 按顺序执行类型检查、自动化测试套件、静态代码检查、代码格式验证及发布包完整性检查:
94
+ ---
90
95
 
91
- ```bash
92
- npm run check
93
- ```
96
+ ## 版本发布
94
97
 
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
- 详细开发贡献流程、发布前检查机制与真实宿主环境手工验收指南,请参阅 [开发与宿主验收指南](./docs/development.md)。
98
+ 参见 [0.1.3 版本说明](./docs/releases/0.1.3.md)。每版 npm 包与 GitHub Release 对应同一个版本 tag,GitHub 附带经过验证的 npm tarball。`main` 可能包含最新发布版之后的改动。
120
99
 
121
100
  ---
122
101
 
123
102
  ## 架构设计
124
103
 
125
- 完整的插件架构图景与交互式全景模型已归档于仓库:
126
-
127
- - **交互式架构全景图**:直接使用浏览器打开 [`docs/architecture/paseo-kanban.architecture.html`](./docs/architecture/paseo-kanban.architecture.html) 查看。
128
- - **架构定义元数据**:[`docs/architecture/paseo-kanban.architecture.json`](./docs/architecture/paseo-kanban.architecture.json)。
104
+ 核心术语和架构决策见 [`CONTEXT.md`](./CONTEXT.md) 与 [`docs/adr/`](./docs/adr/)。
129
105
 
130
106
  ---
131
107
 
@@ -6,6 +6,7 @@ import {
6
6
  Pressable,
7
7
  StyleSheet,
8
8
  ActivityIndicator,
9
+ type LayoutChangeEvent,
9
10
  } from "react-native";
10
11
  import { useSettings } from "@getpaseo/plugin/client";
11
12
  import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
@@ -25,8 +26,23 @@ import {
25
26
  } from "./kanban-session";
26
27
  import { useI18n } from "./i18n";
27
28
 
28
- export function KanbanBoardView(props: PluginSurfaceProps) {
29
- const { theme, layout } = props;
29
+ interface KanbanBoardProps extends PluginSurfaceProps {
30
+ initialProjectId?: string | null;
31
+ }
32
+
33
+ export function KanbanBoardView(props: KanbanBoardProps) {
34
+ const { theme } = props;
35
+ const [panelWidth, setPanelWidth] = useState<number | null>(null);
36
+ const compact =
37
+ props.layout.compact || (panelWidth !== null && panelWidth < 600);
38
+ const layout = useMemo(
39
+ () => ({ ...props.layout, compact }),
40
+ [props.layout, compact],
41
+ );
42
+ const handleLayout = (event: LayoutChangeEvent) => {
43
+ const { width } = event.nativeEvent.layout;
44
+ if (width > 0) setPanelWidth(width);
45
+ };
30
46
  const { t, language } = useI18n(props);
31
47
  const settings = useSettings(kanbanSettings);
32
48
  const {
@@ -35,13 +51,29 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
35
51
  isError: projectsError,
36
52
  } = useProjects();
37
53
 
38
- const [selectedProjectId, setSelectedProjectId] = useState<string>("all");
54
+ const [selectedProjectId, setSelectedProjectId] = useState(
55
+ props.initialProjectId || "all",
56
+ );
39
57
  const [activeTaskSession, setActiveTaskSession] =
40
58
  useState<TaskEditSession | null>(null);
41
59
  const [activeLaneSession, setActiveLaneSession] =
42
60
  useState<LaneEditSession | null>(null);
43
61
  const [reorderError, setReorderError] = useState<string | null>(null);
44
62
 
63
+ const selectableProjects = useMemo(() => {
64
+ const missingIds = new Set([props.initialProjectId, selectedProjectId]);
65
+ const missingProjects = [...missingIds]
66
+ .filter(
67
+ (id): id is string =>
68
+ !!id &&
69
+ id !== "all" &&
70
+ id !== "unassigned" &&
71
+ !projects.some((project) => project.projectId === id),
72
+ )
73
+ .map((id) => ({ projectId: id, projectDisplayName: id }));
74
+ return [...projects, ...missingProjects];
75
+ }, [projects, props.initialProjectId, selectedProjectId]);
76
+
45
77
  const isReady = settings.status === "ready";
46
78
  const board = isReady ? settings.values : null;
47
79
  const revision = isReady ? settings.revision : "";
@@ -154,6 +186,8 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
154
186
  StyleSheet.create({
155
187
  container: {
156
188
  flex: 1,
189
+ minWidth: 0,
190
+ minHeight: 0,
157
191
  backgroundColor: theme.colors.surface0,
158
192
  userSelect: "none",
159
193
  },
@@ -174,6 +208,9 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
174
208
  },
175
209
  titleRow: {
176
210
  flexDirection: "row",
211
+ flexWrap: "wrap",
212
+ flexShrink: 1,
213
+ maxWidth: "100%",
177
214
  alignItems: "center",
178
215
  gap: 8,
179
216
  },
@@ -184,6 +221,8 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
184
221
  },
185
222
  headerActions: {
186
223
  flexDirection: "row",
224
+ flexWrap: "wrap",
225
+ maxWidth: "100%",
187
226
  alignItems: "center",
188
227
  gap: 8,
189
228
  },
@@ -270,6 +309,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
270
309
  },
271
310
  lanesWrapper: {
272
311
  flex: 1,
312
+ minHeight: 0,
273
313
  position: "relative",
274
314
  userSelect: "none",
275
315
  },
@@ -370,7 +410,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
370
410
  if (!board) return null;
371
411
 
372
412
  return (
373
- <View style={styles.container}>
413
+ <View style={styles.container} onLayout={handleLayout}>
374
414
  {/* Top Header */}
375
415
  <View style={styles.header}>
376
416
  <View style={styles.headerTopRow}>
@@ -462,7 +502,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
462
502
  </Text>
463
503
  </Pressable>
464
504
 
465
- {projects.map((proj) => {
505
+ {selectableProjects.map((proj) => {
466
506
  const isSelected = selectedProjectId === proj.projectId;
467
507
  return (
468
508
  <Pressable
@@ -567,7 +607,7 @@ export function KanbanBoardView(props: PluginSurfaceProps) {
567
607
  open={activeTaskSession !== null}
568
608
  onClose={() => setActiveTaskSession(null)}
569
609
  session={activeTaskSession}
570
- projects={projects}
610
+ projects={selectableProjects}
571
611
  theme={theme}
572
612
  layout={layout}
573
613
  />
@@ -1,4 +1,9 @@
1
1
  import { useRef, useSyncExternalStore, useEffect } from "react";
2
+ import type {
3
+ GestureResponderHandlers,
4
+ PanResponderCallbacks,
5
+ PanResponderInstance,
6
+ } from "react-native";
2
7
  import type { KanbanTask } from "../shared/kanban";
3
8
 
4
9
  export interface Rect {
@@ -85,7 +90,6 @@ export interface TaskPreviewItem {
85
90
 
86
91
  export interface KanbanDragOptions {
87
92
  lanes?: ({ id: string } | string)[];
88
- onMoveTask?: (taskId: string, targetLaneId: string) => void | Promise<void>;
89
93
  onReorderTask?: (
90
94
  taskId: string,
91
95
  targetLaneId: string,
@@ -98,16 +102,17 @@ export interface ContainerRefTarget {
98
102
  measureInWindow?: (
99
103
  callback: (x: number, y: number, width: number, height: number) => void,
100
104
  ) => void;
105
+ scrollTo?: (options: { x: number; animated: boolean }) => void;
101
106
  }
102
107
 
103
108
  export interface PanResponderFactory {
104
- create: (config: any) => { panHandlers: any };
109
+ create: (config: PanResponderCallbacks) => PanResponderInstance;
105
110
  }
106
111
 
107
112
  export interface CardDragBinding {
108
113
  readonly isDragging: boolean;
109
- readonly cardPanHandlers: any;
110
- readonly handlePanHandlers: any;
114
+ readonly cardPanHandlers?: GestureResponderHandlers;
115
+ readonly handlePanHandlers?: GestureResponderHandlers;
111
116
  readonly onLayout: (rect: Rect) => void;
112
117
  readonly onUnmount: () => void;
113
118
  }
@@ -169,10 +174,6 @@ export class KanbanDragController {
169
174
  private containerRef: ContainerRefTarget | null = null;
170
175
  private scrollX: number = 0;
171
176
  private activeSession: ActiveDragSession | null = null;
172
- private onMoveTask?: (
173
- taskId: string,
174
- targetLaneId: string,
175
- ) => void | Promise<void>;
176
177
  private onReorderTask?: (
177
178
  taskId: string,
178
179
  targetLaneId: string,
@@ -193,7 +194,10 @@ export class KanbanDragController {
193
194
  private cancelHandler?: () => void;
194
195
  private panResponderFactory: PanResponderFactory | null = null;
195
196
  private cardCallbacks: Map<string, () => void> = new Map();
196
- private cardResponders: Map<string, { body: any; handle: any }> = new Map();
197
+ private cardResponders: Map<
198
+ string,
199
+ { body: PanResponderInstance; handle: PanResponderInstance }
200
+ > = new Map();
197
201
  private pendingInputKind: "mouse" | "touch" = "mouse";
198
202
  private operationSeq: number = 0;
199
203
  private currentOperationId: string | null = null;
@@ -222,9 +226,6 @@ export class KanbanDragController {
222
226
  if (options?.lanes) {
223
227
  this.setLanes(options.lanes);
224
228
  }
225
- if (options?.onMoveTask) {
226
- this.setOnMoveTask(options.onMoveTask);
227
- }
228
229
  if (options?.onReorderTask) {
229
230
  this.setOnReorderTask(options.onReorderTask);
230
231
  }
@@ -244,12 +245,6 @@ export class KanbanDragController {
244
245
  }
245
246
  };
246
247
 
247
- setOnMoveTask = (
248
- fn: (taskId: string, targetLaneId: string) => void | Promise<void>,
249
- ) => {
250
- this.onMoveTask = fn;
251
- };
252
-
253
248
  setOnReorderTask = (
254
249
  fn: (
255
250
  taskId: string,
@@ -428,7 +423,7 @@ export class KanbanDragController {
428
423
  const body = factory.create({
429
424
  onStartShouldSetPanResponder: () => true,
430
425
  onMoveShouldSetPanResponder: () => true,
431
- onPanResponderGrant: (_e: any, gs: any) => {
426
+ onPanResponderGrant: (_e, gs) => {
432
427
  const cb = this.cardCallbacks.get(key);
433
428
  this.handlePointerDown(
434
429
  { taskId, laneId, onPress: cb },
@@ -436,7 +431,7 @@ export class KanbanDragController {
436
431
  "body",
437
432
  );
438
433
  },
439
- onPanResponderMove: (_e: any, gs: any) => {
434
+ onPanResponderMove: (_e, gs) => {
440
435
  this.handlePointerMove({ x: gs.moveX, y: gs.moveY });
441
436
  },
442
437
  onPanResponderRelease: () => this.handlePointerUp(),
@@ -449,7 +444,7 @@ export class KanbanDragController {
449
444
  const handle = factory.create({
450
445
  onStartShouldSetPanResponder: () => true,
451
446
  onMoveShouldSetPanResponder: () => true,
452
- onPanResponderGrant: (_e: any, gs: any) => {
447
+ onPanResponderGrant: (_e, gs) => {
453
448
  const cb = this.cardCallbacks.get(key);
454
449
  this.handlePointerDown(
455
450
  { taskId, laneId, onPress: cb },
@@ -457,7 +452,7 @@ export class KanbanDragController {
457
452
  "handle",
458
453
  );
459
454
  },
460
- onPanResponderMove: (_e: any, gs: any) => {
455
+ onPanResponderMove: (_e, gs) => {
461
456
  this.handlePointerMove({ x: gs.moveX, y: gs.moveY });
462
457
  },
463
458
  onPanResponderRelease: () => this.handlePointerUp(),
@@ -899,8 +894,6 @@ export class KanbanDragController {
899
894
  drop.targetLaneId,
900
895
  drop.targetIndex,
901
896
  );
902
- } else if (this.onMoveTask && drop.targetLaneId !== drop.sourceLaneId) {
903
- await this.onMoveTask(drop.taskId, drop.targetLaneId);
904
897
  }
905
898
  }
906
899
  } finally {
@@ -1358,7 +1351,7 @@ export interface UseKanbanDragReturn {
1358
1351
  getTaskPreview?: (taskId: string) => TaskPreviewItem | null;
1359
1352
  setGetTaskPreview?: (fn?: (taskId: string) => TaskPreviewItem | null) => void;
1360
1353
  bindLane: (laneId: string) => LaneDragBinding;
1361
- bindContainerRef: (instance: any) => void;
1354
+ bindContainerRef: (instance: ContainerRefTarget | null) => void;
1362
1355
  handleContainerLayout: (layout: Rect) => void;
1363
1356
  handleContainerScroll: (scrollX: number) => void;
1364
1357
  getContainerBounds: () => Rect | null;
@@ -1387,21 +1380,17 @@ export interface UseKanbanDragReturn {
1387
1380
  export function useKanbanDrag(
1388
1381
  options: KanbanDragOptions = {},
1389
1382
  ): UseKanbanDragReturn {
1390
- const { lanes = [], onMoveTask, onReorderTask, getTaskPreview } = options;
1383
+ const { lanes = [], onReorderTask, getTaskPreview } = options;
1391
1384
  const controllerRef = useRef<KanbanDragController | null>(null);
1392
1385
  if (!controllerRef.current) {
1393
1386
  controllerRef.current = new KanbanDragController({
1394
1387
  lanes,
1395
- onMoveTask,
1396
1388
  onReorderTask,
1397
1389
  });
1398
1390
  }
1399
1391
  const controller = controllerRef.current;
1400
1392
 
1401
1393
  controller.setLanes(lanes);
1402
- if (onMoveTask) {
1403
- controller.setOnMoveTask(onMoveTask);
1404
- }
1405
1394
  if (onReorderTask) {
1406
1395
  controller.setOnReorderTask(onReorderTask);
1407
1396
  }
@@ -1427,7 +1416,7 @@ export function useKanbanDrag(
1427
1416
  controller.getDroppingState,
1428
1417
  );
1429
1418
 
1430
- const containerScrollRef = useRef<any>(null);
1419
+ const containerScrollRef = useRef<ContainerRefTarget | null>(null);
1431
1420
  const currentScrollX = useRef(0);
1432
1421
 
1433
1422
  useEffect(() => {
@@ -1448,7 +1437,10 @@ export function useKanbanDrag(
1448
1437
  if (!curVelocity || !liveFeedback.isDragging) return;
1449
1438
  const nextOffset = Math.max(0, currentScrollX.current + curVelocity * 14);
1450
1439
  currentScrollX.current = nextOffset;
1451
- containerScrollRef.current?.scrollTo({ x: nextOffset, animated: false });
1440
+ containerScrollRef.current?.scrollTo?.({
1441
+ x: nextOffset,
1442
+ animated: false,
1443
+ });
1452
1444
  }, 16);
1453
1445
 
1454
1446
  return () => clearInterval(timer);
@@ -1463,7 +1455,7 @@ export function useKanbanDrag(
1463
1455
  controller.handleScroll(scrollX);
1464
1456
  };
1465
1457
 
1466
- const bindContainerRef = (instance: any) => {
1458
+ const bindContainerRef = (instance: ContainerRefTarget | null) => {
1467
1459
  containerScrollRef.current = instance;
1468
1460
  controller.bindContainerRef(instance);
1469
1461
  };
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef, useSyncExternalStore, memo } from "react";
2
2
  import { Animated, View, StyleSheet } from "react-native";
3
+ import type { ViewStyle } from "react-native";
3
4
  import type { PluginSurfaceProps } from "@getpaseo/plugin/client";
4
5
  import type { KanbanTask } from "../shared/kanban";
5
6
  import { KanbanCardPreview } from "./kanban-card";
@@ -151,7 +152,7 @@ function KanbanDragOverlayInner({
151
152
  const containerY = containerBounds?.y ?? 0;
152
153
  const cardHalfWidth = layout.compact ? 125 : 140;
153
154
 
154
- let overlayStyle: any;
155
+ let overlayStyle: Animated.WithAnimatedValue<ViewStyle>;
155
156
  if (curDropping && animPos && animRotate && animScale) {
156
157
  overlayStyle = {
157
158
  left: animPos.x,
@@ -0,0 +1,13 @@
1
+ import {
2
+ useWorkspace,
3
+ type PluginWorkspacePanelProps,
4
+ } from "@getpaseo/plugin/client";
5
+ import { KanbanBoardView } from "./kanban-board";
6
+
7
+ export function KanbanWorkspacePanel(props: PluginWorkspacePanelProps) {
8
+ const projectId = useWorkspace(
9
+ props.workspaceId,
10
+ (workspace) => workspace.projectId,
11
+ );
12
+ return <KanbanBoardView {...props} initialProjectId={projectId} />;
13
+ }
@@ -48,7 +48,9 @@ export function fetchProjectsWithOrderControl(
48
48
  let isDestroyed = false;
49
49
 
50
50
  return {
51
- execute: async (fetchFn: () => Promise<any>): Promise<void> => {
51
+ execute: async (
52
+ fetchFn: () => Promise<{ projects?: ProjectItem[] | null } | null>,
53
+ ): Promise<void> => {
52
54
  const requestId = ++latestRequestId;
53
55
  try {
54
56
  const result = await fetchFn();
@@ -56,7 +58,7 @@ export function fetchProjectsWithOrderControl(
56
58
  return;
57
59
  }
58
60
  onUpdate({
59
- projects: (result?.projects ?? []) as ProjectItem[],
61
+ projects: result?.projects ?? [],
60
62
  error: null,
61
63
  isLoading: false,
62
64
  });
package/index.client.tsx CHANGED
@@ -1,33 +1,45 @@
1
1
  import type { PluginClientContext } from "@getpaseo/plugin/client";
2
2
  import { KanbanBoardView } from "./client/kanban-board";
3
+ import { KanbanWorkspacePanel } from "./client/kanban-workspace-panel";
3
4
  import { t, getLanguage, subscribeLanguage } from "./client/i18n";
4
5
 
5
6
  export default function contribute(client: PluginClientContext) {
6
- client.addSurface("kanban", KanbanBoardView);
7
-
7
+ const removeSurface = client.addSurface("kanban", KanbanBoardView);
8
8
  let currentLang = getLanguage();
9
- let removeSidebar = client.addSidebarItem({
10
- id: "kanban",
11
- title: t("kanban.sidebarTitle", currentLang),
12
- icon: "PanelsTopLeft",
13
- surface: "kanban",
14
- });
15
9
 
10
+ const registerEntries = () => {
11
+ const title = t("kanban.sidebarTitle", currentLang);
12
+ const removeSidebar = client.addSidebarItem({
13
+ id: "kanban",
14
+ title,
15
+ icon: "PanelsTopLeft",
16
+ surface: "kanban",
17
+ });
18
+ const removePanel = client.addWorkspacePanel({
19
+ id: "kanban",
20
+ title,
21
+ icon: "PanelsTopLeft",
22
+ context: "workspace",
23
+ Component: KanbanWorkspacePanel,
24
+ });
25
+ return () => {
26
+ removeSidebar();
27
+ removePanel();
28
+ };
29
+ };
30
+
31
+ let removeEntries = registerEntries();
16
32
  const unsubscribe = subscribeLanguage((newLang) => {
17
33
  if (newLang !== currentLang) {
18
34
  currentLang = newLang;
19
- removeSidebar();
20
- removeSidebar = client.addSidebarItem({
21
- id: "kanban",
22
- title: t("kanban.sidebarTitle", newLang),
23
- icon: "PanelsTopLeft",
24
- surface: "kanban",
25
- });
35
+ removeEntries();
36
+ removeEntries = registerEntries();
26
37
  }
27
38
  });
28
39
 
29
40
  return () => {
30
41
  unsubscribe();
31
- removeSidebar();
42
+ removeEntries();
43
+ removeSurface();
32
44
  };
33
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-kanban",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A lightweight, responsive Kanban board plugin for Paseo workspaces",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,6 +20,7 @@
20
20
  "board"
21
21
  ],
22
22
  "files": [
23
+ "OVERVIEW.md",
23
24
  "paseo-plugin.json",
24
25
  "index.client.tsx",
25
26
  "index.server.ts",
@@ -33,6 +34,7 @@
33
34
  "format:check": "prettier --check .",
34
35
  "format": "prettier --write .",
35
36
  "check:package": "node scripts/check-package.mjs",
37
+ "verify:release": "node scripts/verify-release.mjs",
36
38
  "check": "npm run typecheck && npm run test && npm run lint && npm run format:check && npm run check:package"
37
39
  },
38
40
  "devDependencies": {