Skip to content

Commit f64c96b

Browse files
Merge pull request #10 from jkrandom-sudo/feat/loop-dialog-ux
feat(tui): silent non-view commands; interactive task list dialog (0.5.0)
2 parents da73483 + 73d9f24 commit f64c96b

15 files changed

Lines changed: 481 additions & 94 deletions

AGENTS.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# AGENTS.md
2+
3+
## Project
4+
5+
opencode-plugin-loop — `/loop` command plugin for OpenCode (fixed / adaptive / maintenance scheduling).
6+
7+
- Source: `src/` (TypeScript, SolidJS TUI views)
8+
- Tests: `tests/` (node:test, runs against the `dist/` build output)
9+
- Build + test: `npm test` (builds to `dist/` via Babel/tsc, then runs the full suite)
10+
11+
## Development workflow (required)
12+
13+
1. **Branch first**: cut a feature branch (e.g. `feat/xxx`) from the default branch; never commit directly to the default branch.
14+
2. **Verify locally**: `npm test` must pass; for TUI / command-behavior changes, also verify end-to-end in OpenCode.
15+
3. **PR and merge**: open a PR with `gh pr create` and merge into the default branch after verification.
16+
4. **Publish to npm**:
17+
- Bump `package.json` semver after merging (feat → minor, fix → patch).
18+
- The npm access token lives in `/Users/wangshuai/Downloads/npm_access_token.txt`; use the most recently issued token in that file.
19+
- Use the token only via environment variables or throwaway publish-time config (e.g. `//registry.npmjs.org/:_authToken`); **never write it into the repo or commit it**; local `.npmrc` must stay out of git.
20+
- Publish with `npm publish`, then confirm via `npm view opencode-plugin-loop version`.

CLAUDE.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# CLAUDE.md
2+
3+
## 项目
4+
5+
opencode-plugin-loop — OpenCode 的 `/loop` 命令插件,支持 fixed / adaptive / maintenance 三种调度模式。
6+
7+
- 源码:`src/`(TypeScript,SolidJS TUI 视图)
8+
- 测试:`tests/`(node:test,运行于 `dist/` 构建产物之上)
9+
- 构建 + 测试:`npm test`(先 Babel/tsc 构建到 `dist/`,再跑全部测试)
10+
11+
## 开发流程(必须遵守)
12+
13+
1. **新分支开发**:所有改动从默认分支切 feature 分支(如 `feat/xxx`),不直接在默认分支上提交。
14+
2. **本地验证**`npm test` 全部通过;涉及 TUI/命令行为的改动需在 OpenCode 中手动端到端验证。
15+
3. **提交 PR 并合并**:验证通过后 `gh pr create`,合并到默认分支。
16+
4. **发布 npm**
17+
- 合并后按语义化版本 bump `package.json` 版本号(feat → minor,fix → patch)。
18+
- npm access token 存放在 `/Users/wangshuai/Downloads/npm_access_token.txt`,使用该文件中最新下发的 token 发布。
19+
- token 只通过环境变量或发布时临时配置使用(如 `//registry.npmjs.org/:_authToken`),**绝不写入仓库、绝不提交**;本地 `.npmrc` 不得进入 git。
20+
- 发布:`npm publish`,发布后 `npm view opencode-plugin-loop version` 确认。

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ A drop-in `/loop` command for [opencode](https://opencode.ai), modeled after Cla
2929
- **Auto-expire** — tasks idle for more than 7 days are removed on load (active tasks never expire)
3030
- **Max 50 concurrent tasks**
3131
- **LLM-callable tools**`loop_schedule`, `loop_status` (session-bound by default)
32-
- **Interactive Loop results**`/loop` results open in a dedicated native dialog instead of writing over the prompt
32+
- **Interactive task list**`/loop list` and `/loop status` open a dedicated native dialog with selectable task rows instead of writing over the prompt; start/cancel/pause/resume stay silent, failures surface as error toasts
3333
- **Clipboard actions** — copy the complete result or copy any displayed task ID with one action
3434
- **Keyboard and mouse navigation** — move with `Up`/`Down` or `Tab`/`Shift+Tab`, hover with the pointer, and activate with `Enter`, `Space`, or a click
35-
- **Responsive layout** — short or narrow terminals keep the dialog inside the viewport with independently scrollable result and action areas
35+
- **Responsive layout** — short or narrow terminals keep the dialog inside the viewport with a scrollable task list
3636
- **Easy dismissal** — choose **Close**, press `q`, or use the native dialog's `Esc` key
3737

3838
## Requirements
@@ -187,15 +187,15 @@ If you try `cancel <id>` for a task owned by another session, you'll get a refus
187187

188188
Two behavioral differences worth knowing: tasks only fire for the **currently active session** (switch sessions and the others wait; switch back and they catch up once), and fixed tasks fire on a 5-second ticker rather than exact wall-clock cron times (up to one ticker period late).
189189

190-
### Interactive result dialog
190+
### Interactive task list dialog
191191

192-
Every `/loop` command result opens in a separate native OpenCode dialog. It keeps task output away from the prompt and provides:
192+
`/loop list` and `/loop status` open a native OpenCode dialog rendering your tasks as a selectable, color-coded list (▶ active, ⏸ paused). Starting, cancelling, pausing, or resuming a task stays silent; failures surface as error toasts. The dialog provides:
193193

194-
- **Copy ID: `<taskId>`** for every distinct task shown in the result
194+
- A highlighted **task row** per task — press `Enter` to copy its task ID
195195
- **Copy all** for the exact complete result text
196196
- **Close** to dismiss the dialog
197197

198-
Use `Up`/`Down` or `Tab`/`Shift+Tab` to change the selected action, then press `Enter` or `Space` to activate it. Moving the mouse over a row selects it, and clicking activates that exact row. A successful **Copy ID** or **Copy all** action shows a confirmation and closes the dialog immediately; if clipboard access fails, the dialog stays open and shows an error. Press `Page Up` or `Page Down` to scroll long result text, or press `q` or `Esc` to close. In short or narrow terminals, the dialog scales to the available viewport and keeps the result and action lists independently scrollable. A newer Loop result replaces the previous Loop dialog rather than stacking another one.
198+
Use `Up`/`Down` or `Tab`/`Shift+Tab` to change the selected row, then press `Enter` or `Space` to activate it. Moving the mouse over a row selects it, and clicking activates that exact row. A successful copy shows a confirmation and closes the dialog immediately; if clipboard access fails, the dialog stays open and shows an error. Press `Page Up` or `Page Down` to scroll long lists, or press `q` or `Esc` to close. In short or narrow terminals, the dialog scales to the available viewport and keeps the list scrollable. A newer Loop result replaces the previous Loop dialog rather than stacking another one.
199199

200200
### Programmatic (LLM tools)
201201

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-plugin-loop",
3-
"version": "0.4.0",
3+
"version": "0.5.0",
44
"description": "/loop command for opencode — run prompts on a schedule (fixed, adaptive, or maintenance), modeled after Claude Code's /loop",
55
"type": "module",
66
"main": "./dist/index.js",

src/runtime-feedback.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ export async function showLoopResult(
7171
result: CommandParseResult,
7272
logger: LoopLogger
7373
): Promise<void> {
74+
const variant = toastVariant(result.message)
75+
// Non-view results (start/cancel/pause/resume/stop-all) stay silent by design;
76+
// only task lists (info) and failures (error) surface a toast.
77+
if (variant === "success") return
7478
try {
7579
await client.tui.showToast({
7680
throwOnError: true,
7781
body: {
7882
title: LOOP_FEEDBACK_TITLE,
7983
message: result.message,
80-
variant: toastVariant(result.message),
84+
variant,
8185
duration: toastDuration(result.message),
8286
},
8387
})

src/tui-dialog-layout.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,39 @@
11
export interface LoopDialogRows {
22
maxHeight: number
33
messageRows: number
4-
actionRows: number
4+
listRows: number
55
}
66

77
export function allocateLoopDialogRows(
88
terminalRows: number,
9-
actionCount: number
9+
itemCount: number,
10+
taskList = false
1011
): LoopDialogRows {
1112
const rows = Math.max(1, Math.floor(terminalRows))
1213
const available = Math.max(1, rows - 4)
1314
const maxHeight = Math.min(28, available, Math.max(6, Math.floor(rows * 0.7)))
14-
const contentRows = Math.max(0, maxHeight - 3)
15+
const items = Math.max(0, Math.floor(itemCount))
16+
17+
if (taskList) {
18+
// header (1) + gaps (2) + hint bar (1) + gap (1)
19+
const contentRows = Math.max(1, maxHeight - 5)
20+
return { maxHeight, messageRows: 0, listRows: Math.min(items, contentRows) }
21+
}
1522

23+
const contentRows = Math.max(0, maxHeight - 3)
1624
if (contentRows < 2) {
17-
return { maxHeight, messageRows: 0, actionRows: contentRows }
25+
return { maxHeight, messageRows: 0, listRows: contentRows }
1826
}
1927

20-
const actionRows = Math.min(
21-
Math.max(0, actionCount),
28+
const listRows = Math.min(
29+
items,
2230
Math.max(1, Math.min(6, Math.floor(contentRows * 0.4)))
2331
)
2432

2533
return {
2634
maxHeight,
27-
messageRows: contentRows - actionRows,
28-
actionRows,
35+
messageRows: contentRows - listRows,
36+
listRows,
2937
}
3038
}
3139

0 commit comments

Comments
 (0)