diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b6c5e7a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,56 @@ +name: Build + +# Manual trigger only. There is no push/pull_request trigger on purpose: +# the project must not depend on push-triggered CI. +on: + workflow_dispatch: + +# Least privilege: the workflow only reads the repository and uploads artifacts. +permissions: + contents: read + +jobs: + build: + name: ${{ matrix.rid }} + runs-on: ${{ matrix.os }} + strategy: + # One failing RID must not cancel the others. + fail-fast: false + matrix: + include: + - rid: win-x64 + os: windows-latest + - rid: linux-x64 + os: ubuntu-latest + - rid: osx-arm64 + os: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.0.x + + - name: Restore + run: dotnet restore YKanBan.slnx + + - name: Build + run: dotnet build YKanBan.slnx -c Release --no-restore + + # A failing test fails the job, so the publish and upload steps below are + # skipped and this RID produces no artifact. + - name: Test + run: dotnet test YKanBan.slnx -c Release --no-build --no-restore + + - name: Publish + run: dotnet publish YKanBan/YKanBan.csproj -c Release -r ${{ matrix.rid }} --self-contained true -o publish/${{ matrix.rid }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: YKanBan-${{ matrix.rid }} + path: publish/${{ matrix.rid }} + if-no-files-found: error diff --git a/README.md b/README.md index de12ab8..7ce16c5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # YKanBan -My personal used local/offline kanban. +A personal, local/offline kanban board. Cross-platform desktop app built with Avalonia UI and MVVM. Each workspace stores its data in a `.ykanban` folder at the project root, Git-style. -WIP. +See [USAGE.md](USAGE.md) for workspaces, backups, the search syntax and command-line usage. + +## Build and test + +``` +dotnet build YKanBan.slnx +dotnet test YKanBan.slnx +``` + +Packages are pinned in `YKanBan/YKanBan.csproj`. diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..e6dd7c2 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,91 @@ +# YKanBan Usage + +YKanBan is a local/offline kanban board. It works like Git in one respect: all data for a managed project lives in a `.ykanban` folder at the project root, so the board travels with the project folder. + +## 1. Workspace + +- A **workspace** is a folder that directly contains a `.ykanban` directory. Only the given folder is checked; parent folders are never searched. +- `.ykanban` contains `ykanban.db` (the SQLite database with the board, cards and tags) and `ykanban.lock` (the exclusive lock file). +- A workspace is opened by exactly one YKanBan instance at a time. Opening the same workspace a second time shows the lock-conflict page. + +### Command line + +``` +ykanban +``` + +- `` is the workspace folder to open. +- Starting without arguments, or with invalid arguments, shows the argument error page. +- If the path does not exist, the folder-missing page is shown. +- If the folder exists but has no `.ykanban`, the not-initialized page is shown with an **Initialize Workspace** button. Initializing creates the database and seeds the preset columns and tags in the current interface language. + +## 2. Do not commit `.ykanban` to Git + +`.ykanban` holds machine-local state and a lock file; it should not be committed. Add it to your **global** gitignore so it is ignored in every repository: + +``` +# ~/.gitignore_global (or %USERPROFILE%\.gitignore_global on Windows) +.ykanban/ +``` + +Then enable the global ignore file once: + +``` +git config --global core.excludesFile ~/.gitignore_global +``` + +## 3. Backing up and migrating a workspace + +The database runs in SQLite WAL mode. A workspace is therefore **three files**, not one: `ykanban.db`, `ykanban.db-wal` and `ykanban.db-shm`. + +To back up or move a workspace, copy **all three files together** while YKanBan is closed. Copying only `ykanban.db` can lose the most recent changes. + +## 4. Search syntax + +The board search runs only when you press the search button (or press Enter in the search box); there is no live search. A blank query shows everything. The clear button (×) empties the box and restores the full board. + +Search keywords (`AND`, `OR`) and qualifier keys are fixed English grammar and are **never localized**. + +### 4.1 Grammar + +``` +query := orExpr +orExpr := andExpr ( OR andExpr )* +andExpr := operand ( (AND)? operand )* // adjacent terms are an implicit AND +operand := atom | '(' orExpr ')' +atom := word | phrase | qualifier +qualifier := key ':' (word | phrase) +key := tag | title | content | id | column +phrase := '"' any text '"' // escapes: \" to literal quote and \\ to literal backslash +``` + +### 4.2 Semantics + +- `AND` (explicit or by adjacency) binds tighter than `OR`; parentheses force grouping. +- Only uppercase `AND` / `OR` are operators. Lowercase `and` / `or` are plain words. To search for the literal words `AND` or `OR`, quote them, for example `"AND"`. +- A bare word or phrase matches a **substring of the title OR the content**. +- `tag:NAME` matches a tag by its **exact** name. +- `column:TITLE` matches a column by its **exact** title. +- `title:TEXT` matches a substring of the card title, and `content:TEXT` matches a substring of the card content. +- `id:N` matches a card by its **exact** numeric id. +- All matching is case-insensitive (SQLite ASCII semantics). +- Quotes allow values with spaces, for example `tag:"release notes"` or `column:"In Progress"`. +- Inside a phrase, `\"` produces a literal quote and `\\` a literal backslash. + +### 4.3 Strict validation + +An invalid expression opens a modal error dialog and **does not run the search**. The error dialog is centered on the parent window; its text is a localized "query expression is invalid" message plus the parser's non-localized English detail. After closing it, the search box keeps what you typed and the board keeps the previous results. + +The following are invalid: + +- unbalanced parentheses, for example `(a` or `a)`; +- a dangling operator, for example `bug AND`; +- consecutive operators, for example `a AND OR b`; +- a qualifier without a value, for example `tag:`; +- an unterminated phrase, for example `"abc`; +- an invalid escape inside a phrase (a backslash not followed by `"` or `\`), for example `tag:"a\x"`; +- an unknown qualifier key. Only `tag`, `title`, `content`, `id` and `column` are valid, and the key is case-sensitive, so `Tag:x` is an error. A word whose colon is not preceded by an identifier (for example `:foo`) is treated as plain text. + +## 5. Export + +The **Export** button writes a human-readable Markdown snapshot of the whole board (columns, cards and the tag table) to a file you choose. The suggested file name and the timestamps in the document follow the current interface language. The export is read-only; there is no import. diff --git a/docs/ACCEPTANCE.md b/docs/ACCEPTANCE.md new file mode 100644 index 0000000..b3c3c7a --- /dev/null +++ b/docs/ACCEPTANCE.md @@ -0,0 +1,58 @@ +# Manual acceptance checklist + +Everything here is verified by hand on the local Windows build. Automated tests cover storage, the search parser/compiler and the export; the UI is verified manually. + +## Startup and workspace + +- [ ] `ykanban ` with a valid workspace opens the board. +- [ ] `ykanban` with no arguments shows the argument error page. +- [ ] `ykanban a b` (more than one argument) shows the argument error page. +- [ ] A path that does not exist shows the folder-missing page. +- [ ] An existing folder without `.ykanban` shows the not-initialized page with an **Initialize Workspace** button; after clicking it the board opens with the preset columns and tags. +- [ ] Opening the same workspace in a second instance shows the lock-conflict page; closing the first instance lets the second open. +- [ ] The window title is ` - YKanBan`, or just `YKanBan` on an error page. + +## Language + +- [ ] A new workspace is seeded with columns and tags in the language active at creation time. +- [ ] Switching the language in Settings shows the "takes effect after restart" notice and the change only applies after a restart (restart-effective item). +- [ ] After restarting in Chinese, the whole UI is Chinese. + +## Theme + +- [ ] Settings offers light / dark / follow system; switching applies immediately and the interface recolors completely (theme three-state item). +- [ ] Cancelling Settings keeps the previous theme; accepting keeps the chosen one. +- [ ] Custom surfaces (top bar, columns, cards, error text) recolor with the theme. +- [ ] Tag colors always show their raw `#RRGGBB` value in both themes. + +## Board + +- [ ] Columns run left to right and cards top to bottom; there is no drag. +- [ ] A card shows the bold `#id` with its title (or just `#id` when untitled), a truncated content preview and colored tag badges; no timestamps. +- [ ] Clicking a card body opens the editor; the card menu opens in place. +- [ ] Add, edit and delete a column and a card; deletes honor the confirmation toggles. +- [ ] The column editor rejects an empty or duplicate title with an inline error. +- [ ] Editing a card with no effective change writes nothing; cancelling writes nothing; closing with unsaved changes asks to discard when that toggle is on. +- [ ] Tag assignment in the card editor adds existing tags and removes them with the ×; the picker only offers tags not already assigned. +- [ ] Move a card to another column; with only one column the picker reports there is nowhere to move. +- [ ] The Display flyout changes column width (narrow/standard/wide/custom) and card/column sort immediately. + +## Search + +- [ ] Pressing the search button and pressing Enter in the search box behave the same. +- [ ] A blank query shows everything; the clear button restores the full board. +- [ ] A valid query filters cards while keeping every column (empty columns stay visible). +- [ ] The filter status icon is dimmed when not filtering and solid while filtering, and its tooltip states the current status. +- [ ] An invalid query opens the error dialog with the localized message plus the English detail; after closing, the typed text and the previous results remain. +- [ ] Search state persists while switching tabs. + +## Tags + +- [ ] The tag tab lists color, name, description and usage count. +- [ ] Add, rename, recolor (with the color picker) and re-describe a tag; the board refreshes after a change. +- [ ] Deleting a tag warns that it will be removed from N cards and honors the confirmation toggle. + +## Export + +- [ ] Export prompts for a file with a language-appropriate default name and writes Markdown with language-appropriate timestamps, matching the current interface language (export-language-follows-UI item). +- [ ] After a successful export a confirmation dialog shows the written path.