feat: add release workflow, packaging script, usage docs and acceptance checklist
This commit is contained in:
1 parent
bd8472f232
commit
8347445f51
9 files changed
+530
-3
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# Shell scripts run under bash on every CI runner, including Windows checkouts with autocrlf.
|
||||||
|
*.sh text eol=lf
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Manual-only release build (PLAN §7.2): no push or pull_request trigger.
|
||||||
|
# Each RID job runs build -> test -> publish/pack -> upload; a failing step
|
||||||
|
# skips the rest of that job, so a RID whose tests fail produces no artifact.
|
||||||
|
# No GitHub Release is created.
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOTNET_NOLOGO: "true"
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: "true"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: ${{ matrix.rid }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
# Every RID stands on its own: one failure does not cancel the others.
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- rid: win-x64
|
||||||
|
os: windows-latest
|
||||||
|
- rid: linux-x64
|
||||||
|
os: ubuntu-latest
|
||||||
|
- rid: osx-x64
|
||||||
|
os: macos-15-intel
|
||||||
|
- rid: osx-arm64
|
||||||
|
os: macos-15
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build YKanBan.slnx -c Release
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: dotnet test YKanBan.slnx -c Release --no-build
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: >
|
||||||
|
dotnet publish YKanBan/YKanBan.csproj -c Release -r ${{ matrix.rid }}
|
||||||
|
--self-contained -o publish/${{ matrix.rid }}
|
||||||
|
|
||||||
|
- name: Pack
|
||||||
|
id: pack
|
||||||
|
run: |
|
||||||
|
version="$(dotnet msbuild YKanBan/YKanBan.csproj -getProperty:Version)"
|
||||||
|
bash packaging/package.sh ${{ matrix.rid }} publish/${{ matrix.rid }} dist "$version"
|
||||||
|
echo "name=YKanBan-$version-${{ matrix.rid }}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ steps.pack.outputs.name }}
|
||||||
|
path: dist/*
|
||||||
|
if-no-files-found: error
|
||||||
@@ -2,4 +2,30 @@
|
|||||||
|
|
||||||
My personal used local/offline kanban.
|
My personal used local/offline kanban.
|
||||||
|
|
||||||
WIP.
|
- Cross-platform desktop app (Windows / Linux / macOS) built with Avalonia.
|
||||||
|
- Git-like storage: the board lives in a `.ykanban` folder inside the project
|
||||||
|
folder it manages; start it with `ykanban <path>`.
|
||||||
|
- One board per workspace with columns, cards and colored tags; strict search
|
||||||
|
expressions (`tag:bug OR title:login`); Markdown export.
|
||||||
|
- English and Simplified Chinese interface; light, dark or system theme.
|
||||||
|
|
||||||
|
See **[USAGE.md](USAGE.md)** for installing, starting YKanBan, backups and
|
||||||
|
Git, and the full search syntax.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Requires the .NET 10 SDK.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet build
|
||||||
|
dotnet test
|
||||||
|
dotnet run --project YKanBan -- <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
Release archives for win-x64, linux-x64, osx-x64 and osx-arm64 are produced
|
||||||
|
by the manually triggered workflow in `.github/workflows/build.yml`;
|
||||||
|
`packaging/package.sh` packs one published RID.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# YKanBan usage
|
||||||
|
|
||||||
|
YKanBan is a local, offline kanban board. Like Git, it keeps its data in a
|
||||||
|
`.ykanban` folder inside the project folder it manages (the **workspace**).
|
||||||
|
Each YKanBan window manages exactly one workspace; several windows can run at
|
||||||
|
the same time on different workspaces.
|
||||||
|
|
||||||
|
- [Installing](#installing)
|
||||||
|
- [Starting YKanBan](#starting-ykanban)
|
||||||
|
- [macOS](#macos)
|
||||||
|
- [Workspaces, Git and backups](#workspaces-git-and-backups)
|
||||||
|
- [Search syntax](#search-syntax)
|
||||||
|
- [Settings file](#settings-file)
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
|
||||||
|
Each release build is a self-contained archive; no .NET runtime is needed.
|
||||||
|
|
||||||
|
| Platform | Archive | Executable |
|
||||||
|
|---|---|---|
|
||||||
|
| Windows x64 | `YKanBan-<version>-win-x64.zip` | `YKanBan.exe` |
|
||||||
|
| Linux x64 | `YKanBan-<version>-linux-x64.tar.gz` | `YKanBan` |
|
||||||
|
| macOS Intel | `YKanBan-<version>-osx-x64.tar.gz` | `YKanBan.app` (see [macOS](#macos)) |
|
||||||
|
| macOS Apple silicon | `YKanBan-<version>-osx-arm64.tar.gz` | `YKanBan.app` (see [macOS](#macos)) |
|
||||||
|
|
||||||
|
Extract the archive anywhere. To type `ykanban <path>` as shown below, put the
|
||||||
|
executable on your `PATH` under that name, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Linux
|
||||||
|
ln -s /opt/YKanBan-<version>-linux-x64/YKanBan ~/.local/bin/ykanban
|
||||||
|
# macOS
|
||||||
|
ln -s /Applications/YKanBan.app/Contents/MacOS/YKanBan /usr/local/bin/ykanban
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows, add the extracted folder to `PATH`; `ykanban` then finds
|
||||||
|
`YKanBan.exe` because Windows ignores case.
|
||||||
|
|
||||||
|
## Starting YKanBan
|
||||||
|
|
||||||
|
The only way to start YKanBan is with the workspace path:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ykanban <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
- Exactly one argument is accepted. A relative path is resolved against the
|
||||||
|
current working directory, so `ykanban .` opens the current folder.
|
||||||
|
- YKanBan looks for `.ykanban` **only in `<path>` itself**; it does not search
|
||||||
|
parent folders the way Git does.
|
||||||
|
- There are no command-line options. An argument starting with `-` is never
|
||||||
|
treated as a path; to open a folder whose name starts with `-`, write it as
|
||||||
|
`./-name`.
|
||||||
|
|
||||||
|
When the board cannot be shown, the whole window shows one of these pages
|
||||||
|
instead:
|
||||||
|
|
||||||
|
| Page | When |
|
||||||
|
|---|---|
|
||||||
|
| Invalid arguments | No argument, more than one argument, an empty argument, or an argument starting with `-` (including `--help`). Shows the usage line. |
|
||||||
|
| Folder not found | `<path>` does not exist or is not a folder. |
|
||||||
|
| No workspace here | `<path>` exists but has no `.ykanban`. The **Initialize workspace** button creates one, with the columns To Do / In Progress / Done (named in the current interface language). |
|
||||||
|
| Workspace locked | Another YKanBan window already has this workspace open. Close that window first. |
|
||||||
|
| Cannot open workspace | The database was created by a newer YKanBan, is damaged or cannot be opened, or a file-system or permission error occurred (also when initializing fails). The English error details can be selected and copied. |
|
||||||
|
|
||||||
|
The window title is `<workspace name> - YKanBan` while a workspace is open and
|
||||||
|
just `YKanBan` on these pages.
|
||||||
|
|
||||||
|
## macOS
|
||||||
|
|
||||||
|
The app is not notarized. After extracting, remove the download quarantine
|
||||||
|
once, or macOS refuses to open it:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
xattr -dr com.apple.quarantine YKanBan.app
|
||||||
|
```
|
||||||
|
|
||||||
|
Double-clicking `YKanBan.app` starts it without a path, so it only shows the
|
||||||
|
**Invalid arguments** page. Start it from a terminal instead, either through
|
||||||
|
Launch Services or by running the executable inside the bundle:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
open -a YKanBan --args /path/to/project
|
||||||
|
/Applications/YKanBan.app/Contents/MacOS/YKanBan /path/to/project
|
||||||
|
```
|
||||||
|
|
||||||
|
`open -a YKanBan` finds the app once it is in `/Applications` (or another
|
||||||
|
folder Launch Services knows about).
|
||||||
|
|
||||||
|
## Workspaces, Git and backups
|
||||||
|
|
||||||
|
- **Do not commit `.ykanban` to Git.** Add it to your global ignore file once,
|
||||||
|
so no repository picks it up. If `git config --global core.excludesFile`
|
||||||
|
prints a file, add the line `.ykanban/` to that file; otherwise create one:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
echo ".ykanban/" >> ~/.gitignore_global
|
||||||
|
git config --global core.excludesFile ~/.gitignore_global
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Back up or move all three database files together**: `ykanban.db`,
|
||||||
|
`ykanban.db-wal` and `ykanban.db-shm` in `.ykanban/`. Recent changes may
|
||||||
|
still be in the `-wal` file; copying only `ykanban.db` can lose them. The
|
||||||
|
simplest safe backup is to close YKanBan and copy the whole `.ykanban`
|
||||||
|
folder.
|
||||||
|
- **Network shares (SMB, NFS, …) are not supported.** The database and the
|
||||||
|
workspace lock rely on local file-system features that do not work reliably
|
||||||
|
over a network. YKanBan does not detect this; keep workspaces on a local
|
||||||
|
disk.
|
||||||
|
- `.ykanban/ykanban.lock` is held while a window has the workspace open and is
|
||||||
|
released automatically when YKanBan exits or crashes.
|
||||||
|
|
||||||
|
## Search syntax
|
||||||
|
|
||||||
|
Type an expression in the search box on the **Board** tab and click
|
||||||
|
**Search** (or press Enter). Searching only happens then; there is no search
|
||||||
|
while typing. The **×** button clears the box and shows every card again.
|
||||||
|
|
||||||
|
- Columns are always shown, even when none of their cards match.
|
||||||
|
- An empty or blank expression shows every card.
|
||||||
|
- After any change (adding, editing, moving or deleting cards, columns or
|
||||||
|
tags), the board re-runs the **last expression that searched successfully**,
|
||||||
|
not whatever is in the box at that moment. Cards that stop matching
|
||||||
|
disappear; new cards that do not match are not shown.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
| Expression | Finds cards… |
|
||||||
|
|---|---|
|
||||||
|
| `login` | whose title or content contains "login" |
|
||||||
|
| `login crash` | containing both words (anywhere in title or content) |
|
||||||
|
| `login OR signup` | containing either word |
|
||||||
|
| `tag:bug title:login` | tagged `bug` whose title contains "login" |
|
||||||
|
| `tag:bug OR tag:ui` | tagged `bug` or `ui` |
|
||||||
|
| `(tag:bug OR tag:ui) column:Done` | tagged `bug` or `ui` in the column titled "Done" |
|
||||||
|
| `"dark mode"` | whose title or content contains "dark mode" |
|
||||||
|
| `tag:"needs review"` | tagged `needs review` |
|
||||||
|
| `id:61` or `id:#61` | number 61 |
|
||||||
|
| `"http://example.com"` | containing the URL (a `:` must be quoted) |
|
||||||
|
| `"AND"` | containing the text "AND" |
|
||||||
|
|
||||||
|
### Grammar
|
||||||
|
|
||||||
|
```
|
||||||
|
query := orExpr
|
||||||
|
orExpr := andExpr ( OR andExpr )*
|
||||||
|
andExpr := operand ( (AND)? operand )* -- neighbours are joined by AND
|
||||||
|
operand := atom | '(' orExpr ')'
|
||||||
|
atom := word | phrase | qualifier
|
||||||
|
qualifier := key ':' value
|
||||||
|
key := tag | title | content | id | column
|
||||||
|
value := word | phrase -- tag / title / content / column
|
||||||
|
| '#'? digit+ -- id: a positive decimal integer
|
||||||
|
word := one or more characters, except whitespace and ( ) " : \ ;
|
||||||
|
not equal to AND or OR
|
||||||
|
phrase := '"' any text '"' -- escapes: \" and \\
|
||||||
|
```
|
||||||
|
|
||||||
|
### Operators
|
||||||
|
|
||||||
|
- `AND` and `OR` are operators **only in uppercase**. `and` and `or` are
|
||||||
|
ordinary words.
|
||||||
|
- Writing words next to each other means `AND`: `a b` is `a AND b`.
|
||||||
|
- `AND` binds tighter than `OR`: `a OR b c` is `a OR (b AND c)`. Use
|
||||||
|
parentheses to group differently: `(a OR b) c`.
|
||||||
|
- To search for the literal text `AND` or `OR`, quote it: `"AND"`,
|
||||||
|
`tag:"OR"`.
|
||||||
|
- The search keywords (`AND`, `OR`, `tag`, `title`, `content`, `id`,
|
||||||
|
`column`) are always English, whatever the interface language. Qualifier
|
||||||
|
keys are lowercase only: `Tag:bug` is an unknown qualifier.
|
||||||
|
|
||||||
|
### What each atom matches
|
||||||
|
|
||||||
|
| Atom | Matches when |
|
||||||
|
|---|---|
|
||||||
|
| word or phrase | the card title **or** content contains the text |
|
||||||
|
| `title:` value | the card title contains the text |
|
||||||
|
| `content:` value | the card content contains the text |
|
||||||
|
| `tag:` value | the card has a tag whose name **equals** the text |
|
||||||
|
| `column:` value | the card is in a column whose title **equals** the text |
|
||||||
|
| `id:` value | the card number equals the value |
|
||||||
|
|
||||||
|
- A qualifier value can be a word (`tag:bug`) or a phrase
|
||||||
|
(`tag:"needs review"`). Use a phrase for values containing spaces, `(`,
|
||||||
|
`)`, `:` or `\`, and for the values `AND` / `OR`.
|
||||||
|
- `id:` takes an unquoted positive integer, optionally prefixed with `#`:
|
||||||
|
`id:61`, `id:#61`.
|
||||||
|
|
||||||
|
### Quoting and escapes
|
||||||
|
|
||||||
|
- A phrase is text in double quotes. Inside it, `\"` stands for a quote and
|
||||||
|
`\\` for a backslash; any other backslash is an error.
|
||||||
|
- Outside quotes, `\` is not allowed at all, and `:` always starts a
|
||||||
|
qualifier. Quote any text containing them: `"C:\\temp"`, `"a:b"`.
|
||||||
|
|
||||||
|
### Case
|
||||||
|
|
||||||
|
All matching ignores case, for letters of every language: `tag:BUG` finds
|
||||||
|
`bug`, and `ÄRGER` finds `ärger`. Text is otherwise compared literally: `%`,
|
||||||
|
`_` and `*` have no special meaning.
|
||||||
|
|
||||||
|
Tag names and column titles must be unique ignoring case for the letters A–Z
|
||||||
|
only. Two tags such as `Ä` and `ä` can therefore both exist, and `tag:ä`
|
||||||
|
matches both.
|
||||||
|
|
||||||
|
### Invalid expressions
|
||||||
|
|
||||||
|
An invalid expression does not search. A dialog shows "The query expression is
|
||||||
|
invalid. Details: …" followed by the parser's message (always in English).
|
||||||
|
After closing it, the box keeps your text for correcting and the board keeps
|
||||||
|
the previous result.
|
||||||
|
|
||||||
|
These are invalid:
|
||||||
|
|
||||||
|
| Problem | Example |
|
||||||
|
|---|---|
|
||||||
|
| Unbalanced parentheses | `(tag:bug`, `tag:bug)` |
|
||||||
|
| An operator with a missing side | `bug AND`, `OR bug` |
|
||||||
|
| Consecutive operators | `a AND OR b` |
|
||||||
|
| A qualifier without a value | `tag:` |
|
||||||
|
| `AND` / `OR` as an unquoted qualifier value | `tag:AND` (write `tag:"AND"`) |
|
||||||
|
| An unterminated phrase | `"dark mode` |
|
||||||
|
| An invalid escape in a phrase | `tag:"a\x"` |
|
||||||
|
| A backslash outside a phrase | `C:\temp` |
|
||||||
|
| An unknown qualifier key | `label:bug`, `Tag:bug`, `http://x` |
|
||||||
|
| An unquoted value containing `:` | `title:a:b` |
|
||||||
|
| An `id:` value that is not an unquoted positive integer | `id:abc`, `id:0`, `id:"61"` |
|
||||||
|
|
||||||
|
## Settings file
|
||||||
|
|
||||||
|
Settings (language, theme, card sort order, column width and the confirmation
|
||||||
|
switches) are stored in `app.json`:
|
||||||
|
|
||||||
|
| Platform | Location |
|
||||||
|
|---|---|
|
||||||
|
| Windows | `%APPDATA%\YKanBan\app.json` |
|
||||||
|
| Linux, macOS | `~/.config/YKanBan/app.json` (`$XDG_CONFIG_HOME/YKanBan/app.json` when that is set) |
|
||||||
|
|
||||||
|
- The file is written when YKanBan exits, and only if a setting was changed in
|
||||||
|
that session. With several windows open, the last one closed after changing
|
||||||
|
a setting wins.
|
||||||
|
- A language change takes effect after restarting YKanBan; a theme change
|
||||||
|
applies immediately.
|
||||||
|
- If the file cannot be parsed, it is saved as `app.json.bak` and the defaults
|
||||||
|
are used. A single invalid value falls back to its default; the other
|
||||||
|
values are kept.
|
||||||
@@ -30,7 +30,7 @@ public sealed class AboutDialogViewModel : ViewModelBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the repository URL.
|
/// Gets the repository URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string RepositoryUrl => "https://github.com/yyc12345/YKanBan";
|
public string RepositoryUrl => "https://code.blumia.cn/yyc12345/YKanBan";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the license name.
|
/// Gets the license name.
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# YKanBan 人工验收清单
|
||||||
|
|
||||||
|
> 对应 PLAN §9 各里程碑的"人工"验证项。按计划 §7.1,窗口、模态、点击流、数据绑定、主题切换、状态页渲染不写自动化测试,一律按本清单人工验证。
|
||||||
|
> 默认在本地 Windows 执行;Linux / macOS 行为验证按 §7.2 推迟至程序完工后,届时用同一清单逐项复核。
|
||||||
|
|
||||||
|
## 准备
|
||||||
|
|
||||||
|
- [ ] `dotnet build` 与 `dotnet test` 通过
|
||||||
|
- [ ] 准备一个空文件夹 `<demo>` 作为演示工作区;以下 `ykanban` 均指 `dotnet run --project YKanBan --` 或发布产物中的可执行文件
|
||||||
|
- [ ] 记下 `app.json` 位置(Windows:`%APPDATA%\YKanBan\app.json`),验收前备份或删除,保证从默认值开始
|
||||||
|
|
||||||
|
## A. 计划指定的四项(M7 必验)
|
||||||
|
|
||||||
|
### A1 语言切换重启生效
|
||||||
|
|
||||||
|
- [ ] 设置 → 语言改为简体中文 → 显示"重启后生效"提示;当前界面**不**变化
|
||||||
|
- [ ] 确认关闭后,退出程序 → `app.json` 中 `Language` 为 `zh-Hans`
|
||||||
|
- [ ] 重启 → 全部界面文本(顶部区、两个 tab、各模态、错误页、排序项、确认对话框)为中文,无残留英文硬编码
|
||||||
|
- [ ] 切回英文 → 重启后恢复英文
|
||||||
|
- [ ] 中文下新初始化的工作区预置列为 待办 / 进行中 / 已完成;切回英文后这些列名**不变**(已是数据)
|
||||||
|
|
||||||
|
### A2 导出语言跟随界面
|
||||||
|
|
||||||
|
- [ ] 英文界面导出:默认文件名 `<工作区名>-export-yyyyMMdd-HHmmss.md`(精确到秒);文件内模板文字为英文,时间格式 `yyyy-MM-dd HH:mm:ss`
|
||||||
|
- [ ] 中文界面导出:默认文件名 `<工作区名>-导出-yyyyMMdd-HHmmss.md`;模板文字为中文,时间格式 `yyyy年M月d日 HH:mm`
|
||||||
|
- [ ] 保存对话框的文件类型名随语言(Markdown document / Markdown 文档)
|
||||||
|
- [ ] 看板处于搜索筛选、非默认排序时导出:文件仍含**全部**列与卡片,列与卡片均按 id 升序;空列保留;无标题卡片为 `### #id`
|
||||||
|
- [ ] 标签总表含全部标签(含未使用的),使用数正确;标签名含 `` ` ``、`|`、换行时转义符合 §5.8
|
||||||
|
- [ ] 文件为 UTF-8 无 BOM;取消保存对话框不写任何文件;保存到无权限位置时弹"导出失败"消息框
|
||||||
|
|
||||||
|
### A3 主题三态切换
|
||||||
|
|
||||||
|
- [ ] 设置 → 主题 亮色 / 暗色 / 跟随系统 依次切换:**即时生效**,无重启提示
|
||||||
|
- [ ] 每种主题下检查:主窗口、看板列与卡片、标签 tab、全部模态、全部错误页颜色完整切换,无亮底暗字/暗底亮字残留
|
||||||
|
- [ ] 标签徽章在两种主题下均显示原色(§5.9 例外),文字颜色保持可读
|
||||||
|
- [ ] 跟随系统:程序运行中切换 Windows 深浅色 → 程序跟随变化
|
||||||
|
- [ ] 重启后主题保持上次选择
|
||||||
|
|
||||||
|
### A4 app.json 多实例脏写
|
||||||
|
|
||||||
|
- [ ] 同时打开两个实例(不同工作区)。实例 1 改排序,实例 2 改列宽;先关实例 1、后关实例 2 → `app.json` 为实例 2 的完整设置(后退出者覆盖,实例 1 的排序改动丢失——已接受)
|
||||||
|
- [ ] 打开两个实例,只在实例 1 改设置;先关实例 1,再关**未改过设置**的实例 2 → `app.json` 保持实例 1 写入的内容(未改设置的实例不写盘)
|
||||||
|
- [ ] 处于错误页(例如参数错误页)的实例退出时不写 `app.json`(可先删除 `app.json`,退出后确认文件未被创建)
|
||||||
|
- [ ] `app.json` 写成非法 JSON → 启动正常、使用默认值,生成 `app.json.bak`;改一次设置退出后 `app.json` 恢复为合法内容
|
||||||
|
- [ ] `app.json` 中单个字段非法(如 `"Theme": "pink"`、列宽 `9999`)→ 仅该字段回退默认,其余设置保留,不生成 `.bak`
|
||||||
|
|
||||||
|
## B. 启动与错误页(M4 / M4R)
|
||||||
|
|
||||||
|
- [ ] 无参数、两个参数、`--help`、`-x`、空字符串参数 → 参数错误页,显示 `Usage: ykanban <path>`
|
||||||
|
- [ ] 不存在的路径、指向文件的路径 → 目录不存在页
|
||||||
|
- [ ] 相对路径(如 `ykanban .`)按当前目录解析
|
||||||
|
- [ ] 无 `.ykanban` 的文件夹 → 未初始化页;点"初始化"后进入看板,预置三列
|
||||||
|
- [ ] 初始化失败(如只读文件夹)→ 工作区打开失败页
|
||||||
|
- [ ] 同一工作区开第二个实例 → 锁冲突页,无任何操作按钮;关闭第一个实例后可正常打开
|
||||||
|
- [ ] 工作区数据库 `user_version` 改为更高值 → 打开失败页,提示版本过新,英文详情可选中复制
|
||||||
|
- [ ] 把 `ykanban.db` 换成垃圾内容 → 打开失败页(数据库无法打开/损坏)
|
||||||
|
- [ ] 窗口标题:正常时 `工作区名 - YKanBan`,错误页时仅 `YKanBan`
|
||||||
|
- [ ] 顶部区:工作区名 + 小一号完整路径;右侧 导出 / 设置 / 关于 同排
|
||||||
|
|
||||||
|
## C. 看板 tab(M5)
|
||||||
|
|
||||||
|
- [ ] 新建列、编辑列(标题 + 描述);空标题、重名(含仅大小写不同,如 `Done` / `done`)→ 模态内提示错误,模态不关、输入保留
|
||||||
|
- [ ] 删除列:确认框提示将删除 N 张卡片;确认后列与卡片一并删除
|
||||||
|
- [ ] 新建卡片、点击卡片编辑:标题可空;内容多行;`#id` / 创建时间 / 修改时间只读显示(新建时不显示)
|
||||||
|
- [ ] 编辑确认但内容未变 → 修改时间不变;改动后确认 → 修改时间刷新
|
||||||
|
- [ ] 有未保存改动时取消或点标题栏关闭 → 丢弃确认(开关开启时)
|
||||||
|
- [ ] 卡片菜单"移动到列":卡片移动,修改时间**不**变
|
||||||
|
- [ ] 删除卡片:确认框(开关开启时)
|
||||||
|
- [ ] 卡片条目:`#id` 加粗与标题同行;无标题只显示 `#id`;内容约 200 字截断;标签彩色徽章;不显示时间
|
||||||
|
- [ ] 标签区:× 移除;"添加标签"嵌套模态可筛选、选中即添加、可反复打开;嵌套模态中新建标签**立即写库**——外层取消后该标签仍出现在标签 tab
|
||||||
|
- [ ] 列宽:窄 240 / 标准 320 / 宽 480 / 手动 160–720;全局生效,重启保持
|
||||||
|
- [ ] 排序:id 升序 / 最后修改时间降序 / 标题升序(空标题在前);只影响卡片,列始终 id 序;重启保持
|
||||||
|
- [ ] 大数据量:用脚本向一列灌入约 10000 张卡片,滚动流畅、打开工作区无明显卡顿(虚拟化)
|
||||||
|
- [ ] 所有模态在父窗口居中;使用系统原生标题栏
|
||||||
|
- [ ] 打开带输入框的模态时,第一个输入框自动获得焦点并全选
|
||||||
|
|
||||||
|
## D. 标签 tab 与搜索(M6)
|
||||||
|
|
||||||
|
- [ ] 标签 tab 条目:颜色徽章 + 名称 + 描述 + 使用数;顶部显示标签总数;无标签时显示空状态文字
|
||||||
|
- [ ] 新建标签:预置色块或手输 `#RRGGBB`;非法颜色、空名、重名(仅大小写不同也算)→ 模态内提示
|
||||||
|
- [ ] 重命名、改色、改描述后,看板上的徽章同步更新
|
||||||
|
- [ ] 删除标签:确认框提示"将从 N 张卡片移除该标签";删除后卡片保留、只是去掉该标签
|
||||||
|
- [ ] 看板写库(编辑/移动/删除卡片等)后切到标签 tab,使用数已更新
|
||||||
|
- [ ] 搜索仅在点"搜索"或按回车时执行;输入过程中不执行、不报错
|
||||||
|
- [ ] 筛选后空列保留显示;× 清空并显示全部;空白表达式 = 全部
|
||||||
|
- [ ] 非法表达式(如 `bug AND`、`tag:`、`(a`、`id:0`)→ 错误对话框"查询表达式有误,详情:…"+ 英文详情,父窗口居中;关闭后搜索框保留原文,看板保持上次结果
|
||||||
|
- [ ] 筛选状态下写库:按**上次成功**的表达式刷新(即使搜索框已改成别的文字);新建但不匹配的卡片不显示
|
||||||
|
- [ ] USAGE.md 搜索语法示例表逐条实测结果与文档一致
|
||||||
|
|
||||||
|
## E. 设置与关于(M4)
|
||||||
|
|
||||||
|
- [ ] 四个确认开关分别关闭后,对应操作不再弹确认;重启保持
|
||||||
|
- [ ] 关于:应用名、版本号、仓库链接(可点击打开浏览器)、许可证
|
||||||
|
|
||||||
|
## F. 发布(M7,视网络条件推迟)
|
||||||
|
|
||||||
|
- [ ] 在代码托管平台手动触发 `Build` workflow;四个 RID 作业均 build → test → 上传产物
|
||||||
|
- [ ] 产物:`YKanBan-<版本>-win-x64.zip`、`-linux-x64.tar.gz`、`-osx-x64.tar.gz`、`-osx-arm64.tar.gz`
|
||||||
|
- [ ] Windows:解压后 `YKanBan.exe <path>` 可直接运行(自包含,无需安装 .NET)
|
||||||
|
- [ ] Linux:解压后 `./YKanBan <path>` 可运行(可执行位保留)
|
||||||
|
- [ ] macOS:解除隔离后 `open -a YKanBan --args <path>` 与直接运行包内可执行文件均可用;双击 `.app` 只显示参数错误页
|
||||||
|
- [ ] 故意让某平台测试失败 → 该 RID 无产物,其余 RID 不受影响
|
||||||
+1
-1
@@ -375,7 +375,7 @@ phrase := '"' 任意文本 '"' (内部支持转义:\" → 字面引号,
|
|||||||
| **M4R v1.9 回补** | 已完成 | 见下方清单 | 本地 Windows build + test 通过;新增页面人工 |
|
| **M4R v1.9 回补** | 已完成 | 见下方清单 | 本地 Windows build + test 通过;新增页面人工 |
|
||||||
| M5 看板界面 | 已完成(待人工验收) | 看板 tab 全部交互与模态(含约束冲突模态内提示、卡片列表虚拟化、嵌套模态新建标签立即写库、写库后按最近成功表达式刷新) | 人工 |
|
| M5 看板界面 | 已完成(待人工验收) | 看板 tab 全部交互与模态(含约束冲突模态内提示、卡片列表虚拟化、嵌套模态新建标签立即写库、写库后按最近成功表达式刷新) | 人工 |
|
||||||
| M6 标签与搜索集成 | 已完成(待人工验收) | 标签 tab、搜索执行与空列保留、导出接线(含文件名/时间戳 i18n 模式) | 人工 |
|
| M6 标签与搜索集成 | 已完成(待人工验收) | 标签 tab、搜索执行与空列保留、导出接线(含文件名/时间戳 i18n 模式) | 人工 |
|
||||||
| M7 发布与文档 | 待办 | 单一 workflow(4 RID 矩阵 build→test→artifact,osx-x64 用 Intel 运行器)文件就绪(结构自查)、根目录 USAGE.md、README(引用 USAGE.md)、人工验收清单(含语言切换重启生效、导出语言跟随界面、主题三态切换、app.json 多实例脏写四项) | workflow 结构自查通过;实际触发与产物验证视网络条件推迟 |
|
| M7 发布与文档 | 已完成(待人工验收;workflow 实际触发推迟) | 单一 workflow(4 RID 矩阵 build→test→artifact,osx-x64 用 Intel 运行器)文件就绪(结构自查)、根目录 USAGE.md、README(引用 USAGE.md)、人工验收清单(含语言切换重启生效、导出语言跟随界面、主题三态切换、app.json 多实例脏写四项) | workflow 结构自查通过;实际触发与产物验证视网络条件推迟 |
|
||||||
|
|
||||||
**M4R 清单**(v1.9 对已完成里程碑的回补,进入 M5 前完成):
|
**M4R 清单**(v1.9 对已完成里程碑的回补,进入 M5 前完成):
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>YKanBan</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>YKanBan</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>cn.blumia.code.yyc12345.ykanban</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>YKanBan</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>@VERSION@</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>@VERSION@</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Packs one published RID into a release archive.
|
||||||
|
#
|
||||||
|
# packaging/package.sh <rid> <publish-dir> <output-dir> <version>
|
||||||
|
#
|
||||||
|
# win-x64 -> YKanBan-<version>-win-x64.zip (folder YKanBan-<version>-win-x64/)
|
||||||
|
# linux-x64 -> YKanBan-<version>-linux-x64.tar.gz (folder YKanBan-<version>-linux-x64/)
|
||||||
|
# osx-* -> YKanBan-<version>-<rid>.tar.gz (YKanBan.app bundle, ad-hoc signed when codesign exists)
|
||||||
|
#
|
||||||
|
# tar.gz keeps the executable bit, which a plain zip artifact upload would lose.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -ne 4 ]; then
|
||||||
|
echo "usage: $0 <rid> <publish-dir> <output-dir> <version>" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
rid="$1"
|
||||||
|
publish_dir="$(cd "$2" && pwd)"
|
||||||
|
mkdir -p "$3"
|
||||||
|
output_dir="$(cd "$3" && pwd)"
|
||||||
|
version="$4"
|
||||||
|
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
name="YKanBan-${version}-${rid}"
|
||||||
|
staging="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$staging"' EXIT
|
||||||
|
|
||||||
|
case "$rid" in
|
||||||
|
win-*)
|
||||||
|
cp -R "$publish_dir" "$staging/$name"
|
||||||
|
# Compress-Archive is available on every Windows host, unlike zip.
|
||||||
|
powershell -NoProfile -Command \
|
||||||
|
"Compress-Archive -Path '$(cygpath -w "$staging/$name" 2>/dev/null || echo "$staging/$name")' -DestinationPath '$(cygpath -w "$output_dir/$name.zip" 2>/dev/null || echo "$output_dir/$name.zip")' -Force"
|
||||||
|
;;
|
||||||
|
linux-*)
|
||||||
|
cp -R "$publish_dir" "$staging/$name"
|
||||||
|
chmod +x "$staging/$name/YKanBan"
|
||||||
|
tar -C "$staging" -czf "$output_dir/$name.tar.gz" "$name"
|
||||||
|
;;
|
||||||
|
osx-*)
|
||||||
|
app="$staging/YKanBan.app"
|
||||||
|
mkdir -p "$app/Contents/MacOS"
|
||||||
|
cp -R "$publish_dir/." "$app/Contents/MacOS/"
|
||||||
|
chmod +x "$app/Contents/MacOS/YKanBan"
|
||||||
|
sed "s/@VERSION@/${version}/g" "$script_dir/macos/Info.plist" > "$app/Contents/Info.plist"
|
||||||
|
if command -v codesign > /dev/null 2>&1; then
|
||||||
|
# Ad-hoc signature: unsigned arm64 code does not run at all.
|
||||||
|
codesign --force --deep --sign - "$app"
|
||||||
|
fi
|
||||||
|
tar -C "$staging" -czf "$output_dir/$name.tar.gz" YKanBan.app
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unsupported RID: $rid" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "packed $rid -> $output_dir"
|
||||||
Reference in new issue
Block a user