Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,141 @@ Shared component CSS classes live in `shared/styles/components.css`. Both fronte
- Changes to `shared/` affect both Desktop and Web. Verify both builds pass.
- When adding a new shared component, follow the Props-in/Callbacks-out pattern.
- When a shared component needs platform-specific behavior, add a slot prop — do not add platform detection.

## New Feature Workflow

Do not assume that every feature belongs on every surface. Before implementation,
analyze where the capability belongs, which layers it affects, what can be
shared, and which artifacts or documentation must follow it. The result may be
Desktop-only, Web-only, shared across both, package-only, or backend-only.

### 1. Feature Impact Analysis

Before editing code, add an impact matrix to the implementation notes or PR.
Mark every row **Yes**, **No**, or **N/A**, and give a short reason. A "No"
decision is valid; it just must be deliberate.

| Area | Questions to answer |
|------|---------------------|
| Desktop app | Does this support local-first/offline vault work, local files, Tauri capabilities, or a focused desktop workflow? What is its entry point and persistence adapter? |
| Web app | Does this require cloud workspaces, collaboration, members/roles, admin, publishing, browser access, or server-owned data? What is its entry point and permission behavior? |
| Shared model/utilities | Do Desktop, Web, the board package, CLI, MCP, or backend need the same types, validation, IDs, parsing, or transformations? |
| Shared UI | Is the interaction and information architecture materially the same on Desktop and Web? Can it stay props-in/callbacks-out without platform branches? |
| Web service/API | Does it change HTTP contracts, auth, persistence, migrations, events, sync, conflicts, budgets, webhooks, or storage? |
| `packages/board-react` | Does the embeddable Kanban package expose or persist the affected board capability? Does its public API, types, bundle, example, or README need to change? |
| CLI/MCP | Should automation or headless users be able to read or mutate the feature? Do tool schemas or help text need updates? |
| In-app Help | Does the feature add or change a user-visible workflow, setting, limitation, or concept covered by the Help Center? |
| Other docs | Do README, API docs, internal design docs, screenshots, examples, or release notes become inaccurate? |
| Release/deployment | Which artifacts must be rebuilt or promoted: Desktop installers, Web image, board package, CLI binaries, or none? |

Use repository searches to support the matrix. Do not infer that a change is
isolated from its filename.

### 2. Surface Decision Rules

Choose surfaces based on product responsibility, not symmetry:

- Prefer **Desktop** for local vault and filesystem workflows, offline behavior,
native OS integration, and single-file editing.
- Prefer **Web** for cloud collaboration, member/role-aware behavior, admin,
publishing, and server-only capabilities.
- Implement on **both** when the same user-owned document or board can be opened
and edited on both surfaces and inconsistent behavior would corrupt, hide, or
surprise users.
- A feature can intentionally differ by surface. Keep the domain model
compatible, then document the UX difference and its reason.
- Do not add placeholder UI to a surface that cannot provide the real behavior.

For each affected surface, define the entry point, default/empty state,
existing-data state, read-only/permission state, persistence path, error
recovery, and user-visible acceptance criteria.

### 3. Decide What To Share

Share the smallest stable layer that is genuinely common:

- Put common types, parsing, validation, stable IDs, migrations, and pure
transformations in `shared/lib/` when multiple consumers need them.
- Put UI in `shared/components/` only when Desktop and Web have substantially
the same interaction. Shared UI must remain props-in/callbacks-out.
- Keep platform data access in adapters: Tauri/filesystem behavior in `src/`,
Web API behavior in `services/jtype-web/frontend/src/`.
- If workflows differ, share primitives or the domain model instead of forcing
a single component full of platform conditionals.
- A shared component is not proof of integration. Each selected host still
needs an explicit entry point, adapter, and persistence test.

### 4. Decide Whether Kanban Package Changes

Update `packages/board-react` when the feature changes board data or behavior
that external embedded boards should expose. In that case:

- Update package source, public props/types, adapters, and localized strings as
applicable.
- Update `packages/board-react/README.md` and the example when consumers need new
setup or behavior guidance.
- Rebuild the checked-in `packages/board-react/dist/` output.
- Run `npm run build` and `npm test` from `packages/board-react/`.

Do not update the package for app-shell-only behavior, Desktop filesystem
features, Web admin UI, or other capabilities the embeddable board cannot use.
Record that decision in the impact matrix.

### 5. Decide Whether Documentation Changes

Update documentation when the feature changes what a user can do, how they find
it, its data model, configuration, permissions, limitations, or recovery flow.

- In-app Help lives in
`services/jtype-web/frontend/src/help/content/`. Update the relevant article
source and maintained locale variants when the user workflow changes.
- Update `packages/board-react/README.md` for public package behavior.
- Update API docs for contract or tool changes.
- Update internal design/ADR material when the architectural decision changes.
- Update screenshots, examples, and release notes when old material would
misrepresent the product.

Docs are not required for invisible refactors with no behavior or contract
change. State why docs are unchanged in the impact matrix.

### 6. Implement And Test The Selected Scope

Implementation and verification follow the matrix; unaffected surfaces are not
modified merely for parity.

- Add unit tests for shared models and transformations.
- Add a real Desktop entry-point/persistence test when Desktop is **Yes**.
- Add a real Web entry-point/API persistence test when Web is **Yes**.
- Add package tests and rebuild `dist/` when `packages/board-react` is **Yes**.
- Add API/Rust tests when service contracts or persistence are **Yes**.
- Test discoverability from the normal default state, not only a fixture where
the feature is already enabled.
- A shared fixture is useful for component behavior but does not replace the
selected host integration tests.

### 7. Review, Release, And Verify

- The PR must include the completed impact matrix, acceptance criteria, tests
run, documentation decisions, migration notes, and visuals for each changed UI
surface.
- Review the user flow from its real entry point and default state.
- Build and release only the affected artifacts identified in the matrix.
- Verify every released artifact comes from a commit containing the change.
- Promote Kubernetes changes with an explicit context and namespace.
- `/health` proves service availability, not feature completion. Run a
feature-specific smoke test on every promoted or published surface, including
persistence after reload where applicable.

### Definition Of Done

A feature is complete when:

- Every impact area has a Yes/No/N/A decision with rationale.
- Every **Yes** area is implemented, tested, documented, and released as needed.
- Shared code is used only where the model or interaction is genuinely shared.
- Every selected UI surface exposes the feature from a discoverable real entry
point and handles its relevant states.
- Package bundles, public types, examples, and Help content are updated when
their impact rows are **Yes**.
- Feature-specific verification passes on the artifacts and environments that
were actually released.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
132 changes: 132 additions & 0 deletions internal-docs/kanban/status-columns-filter-redesign.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Vertical swimlanes and lightweight filters

## Feature impact analysis

| Area | Decision | Rationale |
|------|----------|-----------|
| Desktop app | Yes | Local `.board` files must expose the same swimlane selection and drag writeback offline. |
| Web app | Yes | Cloud boards use the same interaction and persisted document model. |
| Shared model/utilities | Yes | One canonical lane resolver maps every host to the correct card field. |
| Shared UI | Yes | Desktop, Web, and the embed render the same `BoardSurface`. |
| Web service/API | No | Existing document save APIs already persist board config and card frontmatter. |
| `packages/board-react` | Yes | The embed ships the shared board and must write the same fields on drop. |
| CLI/MCP | No | Their status/card mutation contracts remain compatible. |
| In-app Help | Yes | The visible model and terminology changed. |
| Release/deployment | Yes | Desktop, Web, and package artifacts must be rebuilt after merge. |

## Product model

JType has one visible board dimension: **vertical swimlanes with their labels
at the top**. There is no second left-side row axis.

The swimlane selector offers:

1. **Status** — default. `To do`, `Doing`, and `Done` are workflow definitions.
Dropping a card changes frontmatter `status`.
2. **Priority** — fixed `Urgent`, `High`, `Medium`, `Low`, and `Unassigned`
columns. Dropping a card changes or clears `priority`.
3. **Assignee** — columns derived from current values plus `Unassigned`.
Dropping a card changes or clears `assignee`.
4. **Custom** — board-owned definitions with immutable IDs, editable names,
colors, and order. Dropping a card changes or clears frontmatter `swimlane`.

Status is the conventional Kanban workflow and remains the default. Priority,
assignee, and custom swimlanes are alternate views, not filters and not an
additional grid dimension.

## Stable identity and custom swimlanes

Custom definitions persist in `.board` configuration:

```json
{
"swimlaneBy": "custom",
"swimlanes": [
{
"key": "lane_platform_12345678",
"name": "Platform",
"color": "#0ea5e9"
}
]
}
```

Cards persist only the immutable key:

```yaml
swimlane: lane_platform_12345678
```

Renaming a swimlane therefore never disconnects its cards. When a definition
is deleted without migrating cards, those cards render in **Unassigned** while
their original IDs remain recoverable.

Priority and Assignee can be converted into editable custom swimlanes. The
conversion creates immutable IDs, migrates cards, and leaves the original
priority or assignee fields unchanged.

## UI design

The board toolbar has one **Swimlanes** selector, then the settings action for
that selection, followed by Sort, Filters, and Search.

- Status shows **Manage statuses**.
- Priority and Assignee show **Make swimlanes editable**.
- Custom shows **Manage custom swimlanes**.

Every swimlane is a fixed-width vertical column. Cards use pointer drag:

- horizontal movement crosses swimlanes and updates the selected field;
- vertical movement reorders cards when Status + Manual sort is active;
- empty swimlanes are valid drop targets;
- read-only boards expose neither drag nor mutation controls.

Custom management uses a shared Headless UI dialog. It supports add, rename,
reorder, color, stable-ID inspection, and deletion with optional card
migration. **Add swimlane** also appears after the final custom column.

## Lightweight filters

Filters focus the current view without changing membership or card data:

- priority (multi-select);
- assignee (multi-select, including Unassigned);
- labels (multi-select);
- due date: overdue, today, next seven days, or no due date;
- blocked cards;
- cards assigned to the current user;
- recoverable cards whose custom swimlane definition is missing.

Values within one dimension use OR; dimensions combine with AND. Filters are
local component state and apply to Board, Table, and Calendar.

## Persisted compatibility

- New Status, Priority, and Assignee selections persist in existing `groupBy`.
- Custom persists as `groupBy: "status"`, existing `swimlaneBy: "custom"`,
plus `swimlanes`; conversion normalizes to the same shape.
- Historical boards that stored `swimlaneBy: "status" | "priority" |
"assignee"` from the retired two-dimensional UI use that value as the single
active vertical swimlane dimension.
- No migration rewrites cards merely to open a board.

## Acceptance criteria

- Desktop, Web, and `jtype-board-react` render identical top-labelled vertical
swimlanes with no left-side row labels.
- Status is the default selection.
- Selecting Status, Priority, Assignee, or Custom produces exactly one set of
vertical columns.
- Dragging a card to another column updates the corresponding frontmatter
field in all three hosts.
- Custom swimlanes can be added, renamed, reordered, recolored, and deleted;
stable IDs remain inspectable.
- Empty and Unassigned swimlanes accept drops.
- Missing custom definitions surface their cards in Unassigned without
destroying the original mapping.
- Desktop and Web builds, shared unit tests, component drag E2E, real Desktop
E2E, real Web E2E, and the package build pass.

## Implemented visual

![Vertical custom swimlanes](mockups/vertical-swimlanes-redesign.png)
2 changes: 2 additions & 0 deletions internal-docs/kanban/swimlane-management-design.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Kanban 可编辑泳道设计

> **已于 2026-07-30 被取代。** 本文保留早期二维数据方案的历史记录;当前产品使用顶部标题的单一纵向泳道,不再渲染左侧横向行。现行设计见 [Vertical swimlanes and lightweight filters](./status-columns-filter-redesign.md)。

状态:Grok + Kimi 评审完成,已实现并通过 Desktop / Web / board-react 验证
日期:2026-07-29

Expand Down
2 changes: 2 additions & 0 deletions internal-docs/kanban/swimlane-management-ui-design.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Kanban 泳道管理 UI 设计

> **已于 2026-07-30 被取代。** 本文保留早期左侧横向泳道 UI 的历史记录;当前产品使用顶部标题的单一纵向泳道。现行设计见 [Vertical swimlanes and lightweight filters](./status-columns-filter-redesign.md)。

状态:Grok + Kimi 评审完成,推荐方案已确认并实现

日期:2026-07-29
Expand Down
18 changes: 12 additions & 6 deletions packages/board-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import 'jtype-board-react/style.css'

The component resolves `boardRef` to the `.board` config document itself
(exact path first, then unique basename anywhere in the workspace), scans the
board folder's card documents, and gives you the full board: columns, cards,
drag-to-move (with document writeback), search/filter/sort, Board/Table/
Calendar views, and a built-in read-only card detail.
board folder's card documents, and gives you the full board: selectable
vertical swimlanes (status, priority, assignee, or custom), cards,
drag-to-move (with document writeback), multi-select filters, search/sort,
Board/Table/Calendar views, and a
built-in read-only card detail.

The wrapper fills its container — **give the parent element a height.**

Expand All @@ -37,6 +39,7 @@ The wrapper fills its container — **give the parent element a height.**
| `token` | `string` | — | Session token, typically `mcp`-scoped (mint via the OAuth device flow or the board Settings → MCP access panel). |
| `client` | `JTypeBoardDataClient` | — | Injected data client; replaces `baseUrl`+`token`. **Memoize it** — a new identity per render remounts the board. |
| `readOnly` | `boolean` | `false` | Hides every mutation affordance (drag, composers, menus). View switching stays usable but is kept local, never written back. |
| `currentUser` | `string` | — | Current user's display name. Enables the personal **My cards** filter. |
| `live` | `boolean` | `true` | Try the live SSE feed; see *Live updates* below. |
| `pollIntervalMs` | `number` | `30000` | Polling cadence (min 5000). |
| `onCardOpen` | `(card: BoardViewCard) => void` | — | Intercept card opens; replaces the built-in read-only detail panel. |
Expand Down Expand Up @@ -130,9 +133,12 @@ successful poll.

## MVP scope

Included: column + card rendering, drag to move/reorder cards (frontmatter
`status`/`position` writeback), card create/delete, board/table/calendar and
swimlane views, search/filter/sort, read-only card detail, localized chrome.
Included: vertical swimlane management and drag-to-move with `status`,
`priority`, `assignee`, or custom `swimlane` frontmatter writeback. Manual
within-lane reorder applies to Status swimlanes; alternate dimensions move
cards between lanes without rewriting workflow order. Also included: card
create/delete, board/table/calendar, multi-select filters, search/sort,
read-only card detail, and localized chrome.
Not yet (flag-gated later): members/assignee options, versions/activity,
ticket badges (the `OCCSV-####` chip — the embed client has no ticket-index
endpoint, so cards never show it even when the board configures `ticketKey`),
Expand Down
15 changes: 9 additions & 6 deletions packages/board-react/dist/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,12 @@ declare type BoardViewConfig = {
/** Board ticket-id prefix (e.g. `OCCSV`) for per-card `OCCSV-3371` ticket links. */
ticketKey?: string;
/**
* Second grouping dimension rendered as horizontal swimlanes (rows) in the
* board view. Must differ from `groupBy`; unset = no swimlanes.
*/
swimlaneBy?: BoardSwimlaneGroupKey;
/** Persistent definitions used when `swimlaneBy === "custom"`. */
* Active custom swimlane mode. Historical configs may also contain
* status/priority/assignee here from the retired two-dimensional layout;
* those values now render as the single vertical swimlane dimension.
*/
swimlaneBy?: BoardSwimlaneGroupKey;
/** Persistent definitions used by custom vertical swimlanes. */
swimlanes?: BoardSwimlane[];
/** Present only while a derived-lane conversion is incomplete/retryable. */
swimlaneMigration?: SwimlaneMigration;
Expand Down Expand Up @@ -159,7 +160,7 @@ export declare class JTypeApiError extends Error {
* `client`) plus `workspaceId`+`boardRef` and it renders the same shared
* BoardSurface the jtype desktop + web apps use, backed by the document API.
*/
export declare function JTypeBoard({ workspaceId, boardRef, baseUrl, token, client: injectedClient, readOnly, live, pollIntervalMs, onCardOpen, onConnectionChange, locale, className, style, }: JTypeBoardProps): ReactElement;
export declare function JTypeBoard({ workspaceId, boardRef, baseUrl, token, client: injectedClient, readOnly, currentUser, live, pollIntervalMs, onCardOpen, onConnectionChange, locale, className, style, }: JTypeBoardProps): ReactElement;

export declare type JTypeBoardConnection = 'live' | 'polling' | 'error';

Expand Down Expand Up @@ -216,6 +217,8 @@ export declare type JTypeBoardProps = {
client?: JTypeBoardDataClient;
/** Hide all mutation affordances (view-only board). Default false. */
readOnly?: boolean;
/** Current user's display name; enables the personal "My cards" filter. */
currentUser?: string;
/**
* Try the live SSE feed (default true). Post PR #45 the feed requires a
* full-scope session token — with an mcp-scoped token the server answers
Expand Down
Loading
Loading