Merge pull request #1572 from BradGroux/chore/release-candidate-6.2.0

chore: prepare verified 6.2.0 release candidate and matched media
This commit is contained in:
Brad Groux 2026-09-07 19:44:52 -05:00 • committed by GitHub
commit bb7f04049d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 963 additions and 159 deletions

View file

@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [6.2.0]
Unpublished release candidate. Veritas Kanban 6.2.0 improves Settings persistence, keyboard and native macOS interactions, and large-board behavior.
### Changed
- Made macOS Settings a reusable window with shared settings content, synchronized changes, and a pending-save check before quitting. Browser Settings remains a modal (#1525, #1542).
- Added permission-aware Settings search and direct control links; added System appearance alongside Light and Dark; compacted secondary board filters on narrow screens (#1539–#1541).
- Loaded compact card records and rendered a measured window for large columns. Full task details load on demand, full-text search remains available, and dependency checks share one index (#1537).
- Updated SQLite notifications by affected row and moved filtering and pagination into repository queries. Extracted recovery scheduling, reconciliation and monitoring into one coordinator (#1536, #1543).
### Fixed
- Connected native menu commands to renderer actions, restored standard macOS menu commands, kept restored windows on available displays, and honored the title-bar double-click preference (#1524, #1529–#1531).
- Preserved pending Settings edits across section changes and closing; retained failed saves for retry and waited for persistence before confirming an import (#1525).
- Kept modified keys and IME composition out of board letter shortcuts. Keyboard selection follows visible order, brings tasks into view, and uses help derived from configured columns (#1526, #1533, #1534).
- Corrected primary-action, filter and code-token contrast; separated browser onboarding from native desktop diagnostics (#1532, #1535).
- Rejected empty or ambiguous task identifiers in CLI/MCP mutations; bounded API waits and retained structured errors. File notification writes preserve recoverable state and report malformed or unreadable data (#1527, #1528, #1538).
### Compatibility
- All maintained package versions move together to 6.2.0. REST API `v1` and existing full-task/summary clients remain compatible. This candidate adds no database migration.
- Existing explicit Light/Dark choices are retained. System appearance is an additional preference. Failed Settings writes need an explicit retry; closing the macOS Settings window keeps its pending edits alive.
- CLI/MCP task suffixes must identify exactly one task. Use the complete task ID when several matches exist.
## [6.1.7] - 2026-09-04
Veritas Kanban 6.1.7 corrects desktop and compact-window layout, popout interactions, and task-editing failures found during the broader UI audit.

View file

@ -10,11 +10,11 @@ Start with a visual Kanban board. Add CLI, MCP, OpenClaw, Squad Chat webhooks, w
[![CI](https://github.com/BradGroux/veritas-kanban/actions/workflows/ci.yml/badge.svg)](https://github.com/BradGroux/veritas-kanban/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Version](https://img.shields.io/badge/version-6.1.7-blue.svg)](CHANGELOG.md)
[![Version](https://img.shields.io/badge/version-6.2.0-blue.svg)](CHANGELOG.md)
[![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue.svg)](https://www.typescriptlang.org/)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
![Veritas Kanban 6.1.7 board, task workspace, Settings, and command palette tour](docs/assets/v6.1.7/board-to-workspace.gif)
![Veritas Kanban 6.2.0 candidate board-to-task interaction](docs/assets/v6.2.0/board-to-workspace.gif)
> 🎬 [Watch the full demo video](https://bradgroux.github.io/veritas-kanban/demo/)
@ -167,15 +167,15 @@ When the board is working, use [Setup Paths](docs/SETUP-PATHS.md) to choose the
Spawn autonomous coding agents on tasks when you choose to connect an agent runner. Track them in real-time with the multi-agent dashboard — status indicators, expandable agent cards, model attribution. Team roster manifests and workspace capability discovery route work to the right agent or trusted workspace before a run starts. Shared live run sessions let workspace members observe an active task run, co-drive with attributed messages, or fork a clean follow-up task without taking over the parent run. Squad Chat gives agents a shared local communication channel with system lifecycle events (spawned, completed, failed). Assign multiple agents per task, set permission levels (Intern/Specialist/Lead), and let them coordinate.
![Agent orchestration board](docs/assets/v6.1.7/board-overview.png)
![Agent orchestration board](docs/assets/v6.2.0/board-overview.png)
![Resizable Workbench panel](docs/assets/v6.1.7/workbench-panel.png)
![Resizable Workbench panel](docs/assets/v6.2.0/workbench-panel.png)
Desktop Board Chat and Squad Chat open in one bounded, resizable right-side
Workbench dock. The single orientation keeps the board, header, close control,
and keyboard recovery paths reachable at every supported window size.
![Squad Chat coordination](docs/assets/v6.1.7/squad-chat.png)
![Squad Chat coordination](docs/assets/v6.2.0/squad-chat.png)
### 🧭 Provider And Cutover Operations
@ -185,7 +185,7 @@ The cutover guide documents a GitHub-backed operating model for Codex and Hermes
Codex can run as the default first-class Veritas agent through local `codex exec`, SDK-backed sessions, GitHub-native `@codex` delegation, workflow-engine steps, review actions, Settings health checks, and MCP access to the board. Ollama Local, Ollama Cloud, and LM Studio Local profiles are optional routing targets for users who want local/server-hosted models or explicit cloud model execution. The docs include a roadmap, SOP, workflow examples, and an AGENTS template so provider-backed work can be started, tracked, reviewed, and released through the same Veritas lifecycle as other agents.
![Agent provider settings](docs/assets/v6.1.7/agent-providers.png)
![Agent provider settings](docs/assets/v6.2.0/agent-providers.png)
### 📊 Customizable Dashboard
@ -203,7 +203,7 @@ Define multi-step agent pipelines as version-controlled YAML. Sequential steps,
Not just cards on a board. Tasks have dependency graphs with cycle detection, crash-recovery checkpointing (auto-sanitizes secrets), observational memory with importance scoring, time tracking, and full activity logs. Enforcement gates (review gates, delegation enforcement, auto-telemetry) add production guardrails — all optional, all toggleable.
![Progressive task workspace](docs/assets/v6.1.7/task-workspace.png)
![Progressive task workspace](docs/assets/v6.2.0/task-workspace.png)
### 🔀 Git-Native Development
@ -872,31 +872,31 @@ Packaged macOS release validation also requires candidate-bound native evidence.
<details>
<summary><strong>Click to expand screenshots and GIFs</strong></summary>
These 6.1.7 captures use release-safe dummy content against the current app surfaces. See the [v6 Visual Tour](docs/V6-VISUAL-TOUR.md) for the maintained media index and capture rules.
These 6.2.0 candidate captures use isolated public-safe tasks. Desktop images show the packaged Mac candidate; mobile images show the browser interface. See the [v6 Visual Tour](docs/V6-VISUAL-TOUR.md) for the maintained media index and capture rules.
### Desktop
| Board and task-workspace tour | Current desktop board |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------- |
| ![6.1.7 board and workspace tour](docs/assets/v6.1.7/board-to-workspace.gif) | ![6.1.7 desktop board](docs/assets/v6.1.7/board-overview.png) |
| ![6.2.0 board and workspace tour](docs/assets/v6.2.0/board-to-workspace.gif) | ![6.2.0 desktop board](docs/assets/v6.2.0/board-overview.png) |
| Workbench panel | Agent provider settings |
| ---------------------------------------------------------------- | ---------------------------------------------------------------- |
| ![6.1.7 Workbench panel](docs/assets/v6.1.7/workbench-panel.png) | ![6.1.7 agent providers](docs/assets/v6.1.7/agent-providers.png) |
| ![6.2.0 Workbench panel](docs/assets/v6.2.0/workbench-panel.png) | ![6.2.0 agent providers](docs/assets/v6.2.0/agent-providers.png) |
| Squad Chat coordination | Notification and reply adapters |
| ------------------------------------------------------ | ---------------------------------------------------------------------------- |
| ![6.1.7 Squad Chat](docs/assets/v6.1.7/squad-chat.png) | ![6.1.7 notification adapters](docs/assets/v6.1.7/notification-adapters.png) |
| ![6.2.0 Squad Chat](docs/assets/v6.2.0/squad-chat.png) | ![6.2.0 notification adapters](docs/assets/v6.2.0/notification-adapters.png) |
| Task workspace | Maintenance Center |
| -------------------------------------------------------------- | ---------------------------------------------------------------------- |
| ![6.1.7 task workspace](docs/assets/v6.1.7/task-workspace.png) | ![6.1.7 Maintenance Center](docs/assets/v6.1.7/maintenance-center.png) |
| ![6.2.0 task workspace](docs/assets/v6.2.0/task-workspace.png) | ![6.2.0 Maintenance Center](docs/assets/v6.2.0/maintenance-center.png) |
### Mobile/PWA
| Mobile board | Mobile board to workspace/settings flow |
| ---------------------------------------------------------- | -------------------------------------------------------- |
| ![6.1.7 mobile board](docs/assets/v6.1.7/mobile-board.png) | ![6.1.7 mobile flow](docs/assets/v6.1.7/mobile-flow.gif) |
| ![6.2.0 mobile board](docs/assets/v6.2.0/mobile-board.png) | ![6.2.0 mobile flow](docs/assets/v6.2.0/mobile-flow.gif) |
</details>

Binary file not shown.

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/cli",
"version": "6.1.7",
"version": "6.2.0",
"description": "CLI for Veritas Kanban task management",
"type": "module",
"bin": {

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/desktop",
"version": "6.1.7",
"version": "6.2.0",
"private": true,
"homepage": "https://github.com/BradGroux/veritas-kanban",
"description": "Veritas Kanban native desktop shell",

View file

@ -2,7 +2,7 @@
Veritas works as a board without any agent runner. When you do enable agents, provider profiles are configured in **Settings -> Agents** and are stored in the same app config for the web app and the macOS desktop app.
![Agent provider settings](assets/v6.1.7/agent-providers.png)
![Agent provider settings](assets/v6.2.0/agent-providers.png)
## Defaults

View file

@ -1,5 +1,11 @@
# Veritas Kanban Desktop Release
> For the 6.2.0 candidate, retain local verification and exact package/media provenance.
> The owner authorized existing GitHub CI capacity without purchases or spending changes.
> Release and advisory publication are approved. Signing and publication still require verification.
> Installed-app replacement and public writing remain separate owner decisions.
> Follow the [current candidate gate](V6-GA-CHECKLIST.md#final-release-validation-commands).
This guide covers desktop packaging paths for macOS GA artifacts and
Linux/Windows preview artifact scaffolding: unsigned PR artifacts, signed
release artifacts, update metadata, and smoke testing.

View file

@ -3,7 +3,7 @@
Complete feature reference for Veritas Kanban, including the v6 release surfaces. Every feature, every API endpoint, every configuration option.
For a quick overview, see the [README](../README.md#-what-makes-veritas-kanban-different).
For current release screenshots and GIFs captured from 6.1.7, see the
For candidate screenshots and GIFs captured from 6.2.0, see the
[v6 Visual Tour](V6-VISUAL-TOUR.md). For troubleshooting, see
[TROUBLESHOOTING.md](TROUBLESHOOTING.md).
@ -89,15 +89,15 @@ For current release screenshots and GIFs captured from 6.1.7, see the
The Kanban board is the central interface — a drag-and-drop workspace that reflects your project's state in real time.
![6.1.7 board to task-workspace tour](assets/v6.1.7/board-to-workspace.gif)
![6.2.0 board to task-workspace tour](assets/v6.2.0/board-to-workspace.gif)
| Settings navigation | Board overview |
| -------------------------------------------------------- | ------------------------------------------------ |
| ![6.1.7 settings](assets/v6.1.7/settings-navigation.png) | ![6.1.7 board](assets/v6.1.7/board-overview.png) |
| ![6.2.0 settings](assets/v6.2.0/settings-navigation.png) | ![6.2.0 board](assets/v6.2.0/board-overview.png) |
| Workbench panel | Mobile/PWA board |
| ----------------------------------------------------- | ----------------------------------------------------- |
| ![6.1.7 Workbench](assets/v6.1.7/workbench-panel.png) | ![6.1.7 mobile board](assets/v6.1.7/mobile-board.png) |
| ![6.2.0 Workbench](assets/v6.2.0/workbench-panel.png) | ![6.2.0 mobile board](assets/v6.2.0/mobile-board.png) |
- **Kanban columns** — Four default columns for compatibility: To Do, In Progress, Blocked, Done; board columns and the default create status can be customized in Settings -> Board & Display
- **Drag-and-drop** — Move tasks between configured columns with [@dnd-kit](https://dndkit.com/); reorder within columns; keyboard users press Space to pick up or drop, use spatial arrow-key movement across populated or empty columns, and press Escape to cancel; custom collision detection (pointerWithin + rectIntersection fallback) supports pointer and keyboard moves; tooltips are suppressed during drag; local state provides real-time column updates
@ -105,11 +105,11 @@ The Kanban board is the central interface — a drag-and-drop workspace that ref
- **Task CRUD** — Create, read, update, and delete tasks through the UI or API
- **Create task dialog** — Quick-create with title, type, priority, project, sprint, and description
![Task workspace](assets/v6.1.7/task-workspace.png)
![Task workspace](assets/v6.2.0/task-workspace.png)
- **Progressive task workspace** — A quick drawer and full-width expanded presentation share Overview, Plan, Run, Results, and History navigation without losing the selected section, attempt, event, edits, scroll position, or board focus
![Task workspace details](assets/v6.1.7/task-workspace.png)
![Task workspace details](assets/v6.2.0/task-workspace.png)
- **Task types** — Configurable type system with icons and color-coded card borders (code, research, content, automation, and custom types)
- **Priority levels** — Low, medium, and high with visual indicators on cards
@ -280,9 +280,9 @@ Integrated git workflow from branch creation to merge.
First-class support for autonomous coding agents.
![Task workspace demo](assets/v6.1.7/task-workspace.png)
![Task workspace demo](assets/v6.2.0/task-workspace.png)
![Agent provider settings](assets/v6.1.7/agent-providers.png)
![Agent provider settings](assets/v6.2.0/agent-providers.png)
- **Agent orchestration** — Start, stop, and monitor AI agents on code tasks from the UI or API
- **Multi-agent support** — Ships with Codex CLI, Codex SDK, Codex app-server, Codex Cloud, Hermes Agent, Claude Code, a generic ACP stdio provider, Amp, Copilot, Gemini, Ollama Local, Ollama Cloud, LM Studio Local, and Veritas profiles; add completely custom agents via Settings → Agents
@ -635,7 +635,7 @@ Real-time agent-to-agent communication channel for multi-agent collaboration. Sh
| Threaded Squad Chat coordination | Human reply adapter settings |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| ![6.1.7 Squad Chat coordination](assets/v6.1.7/squad-chat.png) | ![6.1.7 notification and reply adapters](assets/v6.1.7/notification-adapters.png) |
| ![6.2.0 Squad Chat coordination](assets/v6.2.0/squad-chat.png) | ![6.2.0 notification and reply adapters](assets/v6.2.0/notification-adapters.png) |
- **WebSocket-powered chat** — Messages broadcast in real time to all connected clients
- **Resizable Workbench dock** — Board Chat and Squad Chat share one bounded right-side dock that isolates chat scrolling and keeps Close, Escape, Back, and Reset Layout recovery available
@ -2087,7 +2087,7 @@ Settings edits are batched across sections and continue saving when you switch t
Modular settings system with focused, permission-aware sections.
![6.1.7 Maintenance Center settings surface](assets/v6.1.7/maintenance-center.png)
![6.2.0 Maintenance Center settings surface](assets/v6.2.0/maintenance-center.png)
The Maintenance API and CLI include governed SQLite journal conversion:
non-mutating previews, admin scheduling, restart-time exclusive execution,

View file

@ -106,7 +106,7 @@ pnpm dev
Web boots on **3000**, API on **3001**. First boot seeds demo tasks so you have something to look at.
![Dev stack running with public-safe 6.1.7 tasks](assets/v6.1.7/board-overview.png)
![Packaged 6.2.0 candidate showing the shared board UI with public-safe tasks](assets/v6.2.0/board-overview.png)
### 4. Run the in-app setup
@ -128,7 +128,7 @@ Visit [http://localhost:3000](http://localhost:3000) → follow the onboarding f
4. Hit **Create** and watch it appear in **Todo**.
5. Drag it to **In Progress** to feel the flow.
![Reviewing a task in the 6.1.7 progressive task workspace](assets/v6.1.7/task-workspace.png)
![Reviewing a task in the 6.2.0 progressive task workspace](assets/v6.2.0/task-workspace.png)
> Need a clean slate? Remove the example tasks: `rm tasks/active/task_example_*.md`
@ -164,7 +164,7 @@ vk list --status todo
CLI commands fully mirror the API and are the fastest way to script agent workflows. Write commands need `VK_API_KEY` unless localhost bypass grants an `agent` or `admin` role.
![Board to task-workspace tour with dummy 6.1.7 tasks](assets/v6.1.7/board-to-workspace.gif)
![Board to task-workspace tour with dummy 6.2.0 tasks](assets/v6.2.0/board-to-workspace.gif)
---
@ -201,7 +201,7 @@ This section is optional. Agents interact through HTTP + WebSocket; nothing is h
- Check `.veritas-kanban/logs/agents.log` for run details
- Confirm UI Agent Status indicator flips back to **Idle**
![Agent-ready task workspace](assets/v6.1.7/task-workspace.png)
![Agent-ready task workspace](assets/v6.2.0/task-workspace.png)
> **Automation tip:** Keep a `prompts/` folder (see below) so agents get consistent instructions for sprint planning, reviews, research, etc.

View file

@ -5,7 +5,7 @@ channels, and rollback limits. The machine-readable harness record at
`GET /api/config/harness-compatibility` is authoritative for exact capability
digests, fixture revisions, and the current host's live state.
Documentation freshness: 2026-09-03 for the verified Veritas Kanban 6.1.6 stable release.
Documentation freshness: 2026-09-07 for the 6.2.0 candidate. The latest published stable release remains 6.1.7.
## Harness Support Tiers
@ -23,23 +23,23 @@ are incompatible with v6.
## Compatibility Matrix
| Component | Supported v6 combination | Detection/evidence | Fail-closed boundary |
| -------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Server, web, shared, CLI, MCP, desktop | All release packages are exactly 6.1.6. | Package manifests, `/api/health.version`, `vk --version`, MCP metadata, desktop bundle/update metadata. | Mixed release packages are unsupported for publication. |
| Public API | REST API remains `v1` at `/api/v1`, with `/api` compatibility aliases where documented. | `X-API-Version`, OpenAPI/reference docs, CLI/MCP smoke. | Unknown API versions or incompatible auth fail before mutation. |
| Buzz Agent | Buzz v0.4.24 commit `710ed9fff57878a1d69f809b80a6ee0416c53fc4`; `buzz-agent 0.1.0`; ACP v1. | Exact initialize identity, capability digest, probe revision, composed Buzz fixtures. | Unknown build, `buzz-acp`, resume, HTTP/SSE MCP, or capability drift blocks. |
| Buzz relay integration | Buzz v0.4.24; NIP-11, NIP-29, NIP-42; optional NIP-43 membership. | Pinned relay compatibility evidence, signed query/event fixtures, mapping state. | Host/TLS drift, unsafe URL, bad signature, identity mismatch, replay, or disabled mapping blocks. |
| Grok Build | v0.2.111 build `94172f2aa4e5`; generic ACP v1. | Exact version/build and `x.ai` capability handshake. | Unknown version, approval bypass, leader/plugin/endpoint injection, or unsupported controls blocks. |
| Codex CLI | Explicit `codex-cli` adapter with exact runtime probe. | `codex --version`, `codex login status`, runtime manifest. | Missing adapter evidence or requested unsupported controls blocks. |
| Codex SDK | `@openai/codex-sdk 0.149.0`. | SDK import plus Codex authentication and runtime manifest. | Missing SDK/authentication or unsupported controls blocks. |
| Codex app-server | `codex-cli 0.145.0`, upstream commit `25af12f7e61572b0bc18ddb1008be543b91519b0`. | Generated v2 schemas, exact version, remote control disabled, deterministic and optional live smoke. | Schema drift, remote control, inherited plugins/apps/hooks/browser/computer tools, or unsandboxed shell method blocks. |
| Claude Code | `2.1.218 (Claude Code)`. | Version, auth status, agent discovery, stream fixtures, optional live smoke. | Permission bypass, inherited config/plugins, unsupported lifecycle request, bad stream, or missing authoritative result blocks. |
| GitHub Copilot CLI | v1.0.74 public-preview ACP; tag commit `2b809c84e87dbcc88f897cb4f3fb97c43b77af95`. | Version and ACP initialize handshake; authentication remains provider-managed. | Version drift, broad allow, remote/plugin/config injection, or unsupported controls blocks. |
| Hermes Agent | v2026.7.7.2 one-shot process adapter. | `hermes --version` and allowlisted boot authentication. | Resume/follow-up remains unsupported. |
| OpenClaw | v2026.6.11 gateway adapter. | Gateway health, runtime manifest, explicit operator tool policy. | Missing `sessions_spawn`/`sessions_send`, unknown evidence, or unsupported task controls blocks. |
| macOS desktop | Signed/notarized macOS 13 Ventura or later arm64 app with bundled 6.1.6 server/web. | Bundle version, signature, Gatekeeper, stapling, `/api/health.version`, update metadata. | Mixed bundle/runtime, failed readiness, signature, or metadata checks blocks stable publication. |
| Linux/Windows desktop | Unsigned preview artifacts only. | Cross-platform packaging workflows. | Not a supported stable install or update channel. |
| Desktop SQLite/profile | Existing v5.2.5 workspace upgraded in place after a complete backup. | Data/profile counts, integrity check, startup normalization, board/runtime smoke. | Competing writers, unsafe filesystem, failed migration, or missing recovery evidence blocks acceptance. |
| Component | Supported v6 combination | Detection/evidence | Fail-closed boundary |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Server, web, shared, CLI, MCP, desktop | All candidate packages are exactly 6.2.0. | Package manifests, `/api/health.version`, `vk --version`, MCP metadata, desktop bundle/update metadata. | Mixed release packages are unsupported for publication. |
| Public API | REST API remains `v1` at `/api/v1`, with `/api` compatibility aliases where documented. | `X-API-Version`, OpenAPI/reference docs, CLI/MCP smoke. | Unknown API versions or incompatible auth fail before mutation. |
| Buzz Agent | Buzz v0.4.24 commit `710ed9fff57878a1d69f809b80a6ee0416c53fc4`; `buzz-agent 0.1.0`; ACP v1. | Exact initialize identity, capability digest, probe revision, composed Buzz fixtures. | Unknown build, `buzz-acp`, resume, HTTP/SSE MCP, or capability drift blocks. |
| Buzz relay integration | Buzz v0.4.24; NIP-11, NIP-29, NIP-42; optional NIP-43 membership. | Pinned relay compatibility evidence, signed query/event fixtures, mapping state. | Host/TLS drift, unsafe URL, bad signature, identity mismatch, replay, or disabled mapping blocks. |
| Grok Build | v0.2.111 build `94172f2aa4e5`; generic ACP v1. | Exact version/build and `x.ai` capability handshake. | Unknown version, approval bypass, leader/plugin/endpoint injection, or unsupported controls blocks. |
| Codex CLI | Explicit `codex-cli` adapter with exact runtime probe. | `codex --version`, `codex login status`, runtime manifest. | Missing adapter evidence or requested unsupported controls blocks. |
| Codex SDK | `@openai/codex-sdk 0.149.0`. | SDK import plus Codex authentication and runtime manifest. | Missing SDK/authentication or unsupported controls blocks. |
| Codex app-server | `codex-cli 0.145.0`, upstream commit `25af12f7e61572b0bc18ddb1008be543b91519b0`. | Generated v2 schemas, exact version, remote control disabled, deterministic and optional live smoke. | Schema drift, remote control, inherited plugins/apps/hooks/browser/computer tools, or unsandboxed shell method blocks. |
| Claude Code | `2.1.218 (Claude Code)`. | Version, auth status, agent discovery, stream fixtures, optional live smoke. | Permission bypass, inherited config/plugins, unsupported lifecycle request, bad stream, or missing authoritative result blocks. |
| GitHub Copilot CLI | v1.0.74 public-preview ACP; tag commit `2b809c84e87dbcc88f897cb4f3fb97c43b77af95`. | Version and ACP initialize handshake; authentication remains provider-managed. | Version drift, broad allow, remote/plugin/config injection, or unsupported controls blocks. |
| Hermes Agent | v2026.7.7.2 one-shot process adapter. | `hermes --version` and allowlisted boot authentication. | Resume/follow-up remains unsupported. |
| OpenClaw | v2026.6.11 gateway adapter. | Gateway health, runtime manifest, explicit operator tool policy. | Missing `sessions_spawn`/`sessions_send`, unknown evidence, or unsupported task controls blocks. |
| macOS desktop | Signed/notarized macOS 13 Ventura or later arm64 app with bundled 6.2.0 server/web for this candidate. | Bundle version, signature, Gatekeeper, stapling, `/api/health.version`, update metadata. | Mixed bundle/runtime, failed readiness, signature, or metadata checks blocks stable publication. |
| Linux/Windows desktop | Unsigned preview artifacts only. | Cross-platform packaging workflows. | Not a supported stable install or update channel. |
| Desktop SQLite/profile | Existing v5.2.5 workspace upgraded in place after a complete backup. | Data/profile counts, integrity check, startup normalization, board/runtime smoke. | Competing writers, unsafe filesystem, failed migration, or missing recovery evidence blocks acceptance. |
## Certification And Invalidation

View file

@ -1,13 +1,24 @@
# Veritas Kanban v6 GA Checklist
This checklist contains the active stable-release gate for Veritas Kanban
6.1.7 and retains the completed 6.1.6, 6.1.5, 6.1.4, 6.1.3, 6.1.2, 6.1.1, 6.1.0, and 6.0.2 evidence below.
This checklist contains the active candidate gate for Veritas Kanban
6.2.0 and retains the completed 6.1.7, 6.1.6, 6.1.5, 6.1.4, 6.1.3, 6.1.2, 6.1.1, 6.1.0, and 6.0.2 evidence below.
Command results, platform details, workflow links, limitations, and artifact hashes belong in
[v6 Release Candidate Evidence Packet](V6-RC-EVIDENCE-PACKET.md).
Documentation freshness: 2026-09-04 for the completed Veritas Kanban 6.1.7 release.
Documentation freshness: 2026-09-07 for the unpublished 6.2.0 candidate.
## 6.1.7 Release Gate
## 6.2.0 Candidate Gate
- [x] Root, shared, server, web, CLI, MCP and desktop package versions are aligned at 6.2.0, with candidate release notes and compatibility guidance.
- [x] Complete the appropriate local integration verification.
- [x] Complete final delivery readback for all 25 findings: 24 implemented fixes and #1544 explicitly dispositioned as not reproducible.
- [x] Inspect the actual packaged candidate, all matched screenshots and complete GIF/video playback; retain revision and package hashes.
- [ ] Complete review and the repository delivery workflow using the owner-authorized existing CI capacity.
- [x] Obtain owner approval for the 6.2.0 release and advisory publication.
- [ ] Obtain separate approval for installed-app replacement and public writing.
- [ ] Verify signing, notarization, distributed assets and the installed application before calling 6.2.0 stable.
## Historical 6.1.7 Release Gate
- [x] macOS UI tracker #1389 and child issues #1378-#1388 are complete through focused merged work, the final signed candidate, refreshed documentation media, and the separately verified installed app.
- [x] Root, shared, server, web, CLI, MCP, desktop, changelog, reviewed release source, and public documentation identify 6.1.7 without an API or database migration.
@ -156,54 +167,51 @@ Documentation freshness: 2026-09-04 for the completed Veritas Kanban 6.1.7 relea
## Final Release Validation Commands
Apply `ci:full` to the release pull request and keep it applied through the
final candidate synchronization. That single milestone runs the complete
workspace suite, critical-path coverage, unsigned desktop artifacts, and
Docker image contract. Run the following commands once from the clean 6.1.7
release candidate at the supported Node floor and current supported Node:
The owner authorized the existing GitHub CI capacity for this candidate without a credit card,
capacity purchase, or spending-limit change. Release publication still requires separate approval.
Run verification locally and retain the actual command, revision and result. Group focused
checks by completed feature and run the assembled integration gate once; repeat only for a
relevant change, failure or unresolved concern.
```bash
pnpm install --frozen-lockfile
pnpm check:pnpm-settings
pnpm check:security-artifacts
pnpm check:delivery-cadence
pnpm test:ci-scope
pnpm check:service-filesystem-boundary
pnpm audit --prod --audit-level=high
pnpm audit:all
pnpm check:gitleaks
pnpm lint
pnpm lint:budget
pnpm lint:report
pnpm qa:mantine
pnpm typecheck
pnpm build
pnpm test
pnpm test:unit
pnpm desktop:test
pnpm test:e2e
pnpm smoke:cli-mcp
pnpm desktop:test
pnpm desktop:build
pnpm desktop:check:electron-artifacts
pnpm desktop:test:readiness
pnpm desktop:dev:fresh
pnpm desktop:smoke:mac:local
pnpm desktop:package:mac:unsigned
pnpm test:release-format
pnpm validate:release -- --version 6.1.7 --skip-build-output --source-only
pnpm validate:release -- --version 6.1.7 --native-evidence /absolute/path/evidence.json --native-app /absolute/path/veritas-kanban.app --docker-build
pnpm test:release-native
pnpm desktop:package:mac:unsigned
node scripts/native-ui/run.mjs /absolute/path/candidate.app /absolute/path/new-native-evidence
node --import tsx scripts/native-ui/board-performance.mjs /absolute/path/candidate.app /absolute/path/new-board-evidence
pnpm docs:capture-media /absolute/path/candidate.app /absolute/path/new-media-evidence
pnpm validate:release -- --version 6.2.0 --skip-build-output --source-only
```
Source preflight is not release acceptance. Full validation requires the exact clean candidate's fresh native matrix and retained screenshots; signing, installed-app, documentation-media, and publication evidence remain separate gates. See [desktop release verification](DESKTOP-RELEASE.md#native-gate-before-macos-upload).
The packaging command includes the workspace build. Source preflight is not release
acceptance. After reviewing and committing the exact captured bytes, run the existing
media-publication verifier, then full release validation with the native report, exact app
and media publication manifest. See [desktop release verification](DESKTOP-RELEASE.md#native-gate-before-macos-upload).
Mount and inspect the unsigned DMG and ZIP, exercise the visible native
single-instance/reopen/clean-close/quit lifecycle with an isolated profile, and
run the production image as its non-root user against an isolated volume.
Record health, auth, SQLite, static-web, canonical-path, backup, integrity,
image-size, and clean-shutdown evidence. The same candidate must pass these
gates at Node 22.22.1 and the current supported Node runtime.
Mount and inspect the unsigned DMG and ZIP, exercise the native single-instance, reopen,
close and quit lifecycle using an isolated profile, and record the supported-runtime checks
actually performed. Signing, notarization, Docker distribution, installed-app replacement
and publication remain distinct evidence boundaries; do not infer them from local tests.
## Distribution And Post-Publication
Version 6.2.0 is not published. The following is retained historical evidence.
The 6.1.7 release is published and verified. Annotated tag `v6.1.7` peels to
`6befd39cbc1264b18fa272d25bc64642f2b60383`; the exact live release body,
signed/notarized assets, updater metadata, retained native matrix,

View file

@ -1,6 +1,6 @@
# Veritas Kanban v6 Release Candidate Evidence Packet
This packet records the completed Veritas Kanban 6.1.7 macOS UI and interaction release and
This packet tracks the unpublished 6.2.0 candidate and the completed 6.1.7 macOS UI and interaction release, and
retains historical evidence for the completed 6.1.6, 6.1.5, 6.1.4, 6.1.3, 6.1.2, 6.1.1, and 6.1.0 releases, the quarantined 6.0.0 prerelease, the 6.0.1
stabilization release, and the 6.0.2 desktop recovery hotfix. It separates
merged implementation, deterministic conformance, local runtime proof, signed
@ -8,7 +8,23 @@ publication, and Homebrew availability.
Veritas Kanban 6.1.7 is the active stable release. Do not use 6.0.0 for installation or upgrade validation.
Documentation freshness: 2026-09-04 for the completed Veritas Kanban 6.1.7 release.
Documentation freshness: 2026-09-07. Local native and media acceptance passed. Audit fixes are merged; release and distribution acceptance remain separate.
## 6.2.0 candidate
The candidate is prepared from the audit backlog delivered through individually reviewed public pull requests. No 6.2.0 release, installation replacement, or distribution update has occurred. The evidence below records the integrated delivery candidate. All 24 implemented audit fixes and the acceptance follow-ups are merged; #1544 is explicitly dispositioned as not reproducible. Tracker #1545 records the delivery readback.
- Package versions: root, shared, server, web, CLI, MCP and desktop identify 6.2.0.
- Focused local checks cover the affected storage, Settings, keyboard, board, native bridge and recovery behavior.
- Verified build: `a2b8fbba0e2851d65611238624d8c9de347a2a27`. Whole-app SHA-256: `476a61cbc81f2a78b76657a87ccc98e2be645d2886d78e5df5fe51d7424dedcb`. The arm64 package was exercised on macOS 26.6.2 and is unsigned; it has not replaced an installed application.
- Local integration: server 3,709 passed / 5 skipped on Node 22.23.2; web 1,032 passed; CLI 90 passed; MCP 80 passed / 19 credential-gated skips; desktop 108 passed. Typecheck, build and lint passed with zero lint errors. All application contents match the retained passing candidate. The capture runner additionally synchronizes the renderer before reading the native frame and waits for the mobile General appearance control to load. Only the order of one Settings regression test changed during delivery; its 14-test file and 56 release-native contract tests passed again. The integrated delivery revision was rebuilt and repackaged, then passed fresh native, large-board, and media acceptance.
- The exact package passed all 144 required native cases and detected all six deliberate faults. Checks include native keyboard input, focus, light/dark and compact layouts, menu routing, modeless Settings persistence, window management and recovery.
- Packaged 100/1,000/5,000-task fixtures passed the recorded performance budgets. The 5,000-task journey also passed offscreen keyboard navigation, full-detail retrieval, status move, complete-description search, realtime updates, pointer dragging and reload persistence.
- All 14 maintained media assets, ten task-mode PNGs and the focused large-board image were captured from this build, inspected, and retained with original hashes. Both GIFs and the demo MP4 completed playback inspection. [Capture provenance](assets/v6.2.0/README.md) and the [comparison gallery](releases/v6.2.0-comparison.md) disclose baseline fixture and environment differences.
- Earlier Node 26.8.1 server runs had intermittent failures. Their cause remains unproven; passing supported-runtime checks do not establish remediation of #1544.
- The original external-tracker approval failure has not recurred in the instrumented full server run. Its original worker/order information was not retained. #1544 was explicitly dispositioned as not reproducible, with the evidence and limits recorded in the issue; no historical fix is claimed.
- The owner explicitly authorized existing GitHub CI capacity for delivery. No credit card, capacity purchase, spending-limit change, or repository-control change was made. Local verification remains the basis of the application acceptance evidence.
- The owner approved the 6.2.0 release and advisory publication. Signing/notarization and destination verification remain pending. Installed-app replacement and public writing remain separate owner decisions.
## 6.1.7 macOS UI and interaction release

View file

@ -1,8 +1,14 @@
# Veritas Kanban 6.1.6 Release Notes
# Veritas Kanban 6.2.0 Candidate Release Notes
The 6.2.0 candidate improves Settings persistence, native macOS interactions, keyboard navigation and large-board behavior. Read the [6.2.0 release source](releases/v6.2.0.md) for current changes and compatibility notes. Verification and publication remain separate stages in the [candidate evidence packet](V6-RC-EVIDENCE-PACKET.md).
The latest published stable release remains 6.1.7. Veritas Kanban 6.0.0 remains quarantined.
## Historical 6.1.6 release
Veritas Kanban 6.1.6 is a focused desktop reliability and interface consistency release. It corrects chat and drag layout overflow, makes consecutive board moves reliable, polishes Settings and Command+K, removes the redundant desktop wordmark, unifies action and selected-navigation styling, refreshes maintained media, and updates vulnerable transitive dependencies.
> Veritas Kanban 6.0.0 remains a quarantined prerelease. Version 6.1.6 is the supported stable v6 release and requires macOS 13 Ventura or later on Apple silicon.
> Veritas Kanban 6.0.0 remains a quarantined prerelease. Version 6.1.6 was a supported stable v6 release and requires macOS 13 Ventura or later on Apple silicon.
## 6.1.6 Desktop Reliability Outcomes

View file

@ -1,13 +1,16 @@
# Veritas Kanban v6 Visual Tour
This tour presents release-safe 6.1.7 views of the board, progressive task
This tour presents release-safe 6.2.0 views of the board, progressive task
workspace, grouped Settings, provider support, communication surfaces,
Maintenance, Command+K, Workbench, and mobile layout. The historical
[v5 Visual Tour](V5-VISUAL-TOUR.md) retains its own versioned media.
Documentation freshness: 2026-09-04 for the verified Veritas Kanban 6.1.7 stable
release. Every image below was generated from the integrated 6.1.7 source
with isolated dummy data.
Documentation freshness: 2026-09-07 for the unpublished 6.2.0 candidate.
Desktop media was captured from the packaged Mac application; mobile media was
captured in a browser at 390×844. All media uses isolated public-safe data.
See the [complete before/after comparison](releases/v6.2.0-comparison.md) and
[capture provenance](assets/v6.2.0/README.md). The signed 6.1.7 release remains
the current installation target until 6.2.0 is separately approved and released.
## Board And Task Workspace
@ -16,11 +19,11 @@ desktop shell, and Workbench affordance. Opening a task replaces the former
peer-tab drawer with progressive Overview, Plan, Run, Results, and History
modes.
![6.1.7 board overview with release-safe tasks](assets/v6.1.7/board-overview.png)
![6.2.0 board overview with release-safe tasks](assets/v6.2.0/board-overview.png)
![6.1.7 progressive task workspace](assets/v6.1.7/task-workspace.png)
![6.2.0 progressive task workspace](assets/v6.2.0/task-workspace.png)
![6.1.7 board, task workspace, grouped Settings, providers, and Command+K tour](assets/v6.1.7/board-to-workspace.gif)
![6.2.0 board-to-task interaction](assets/v6.2.0/board-to-workspace.gif)
## Grouped Settings And Command Palette
@ -28,9 +31,9 @@ Settings groups all twenty destinations under Core, Collaboration, Automation,
Governance, and System while keeping transfer and danger-zone actions separate.
Command+K uses the same current shell and remains bounded to the viewport.
![6.1.7 grouped Settings navigation](assets/v6.1.7/settings-navigation.png)
![6.2.0 grouped Settings navigation](assets/v6.2.0/settings-navigation.png)
![6.1.7 Command+K palette](assets/v6.1.7/command-palette.png)
![6.2.0 Command+K palette](assets/v6.2.0/command-palette.png)
## Provider Support
@ -43,7 +46,7 @@ diagnostics, dispatch, and telemetry.
The release capture must use dummy profile names and contain no login state,
environment values, provider output, private paths, or credentials.
![6.1.7 provider settings with current grouped navigation](assets/v6.1.7/agent-providers.png)
![6.2.0 provider settings with current grouped navigation](assets/v6.2.0/agent-providers.png)
Expected visible behavior:
@ -62,7 +65,7 @@ URLs, public identity, environment-variable references, compatibility facets,
one-channel mappings, definition preview/import, trigger rules, and bounded
audit state.
![6.1.7 communication health, Buzz, and reply-adapter settings](assets/v6.1.7/notification-adapters.png)
![6.2.0 communication health, Buzz, and reply-adapter settings](assets/v6.2.0/notification-adapters.png)
Expected visible behavior:
@ -97,22 +100,31 @@ Expected visible behavior:
## Workbench, Squad Chat, Maintenance, And Mobile
| Surface | Current 6.1.7 capture |
| Surface | Current 6.2.0 capture |
| ------------------ | ----------------------------------------------------------------- |
| Workbench | ![Workbench](assets/v6.1.7/workbench-panel.png) |
| Squad Chat | ![Squad Chat](assets/v6.1.7/squad-chat.png) |
| Maintenance | ![Maintenance](assets/v6.1.7/maintenance-center.png) |
| Mobile board | ![Mobile board](assets/v6.1.7/mobile-board.png) |
| Mobile task | ![Mobile task workspace](assets/v6.1.7/mobile-task-workspace.png) |
| Mobile Settings | ![Mobile Settings](assets/v6.1.7/mobile-settings.png) |
| Mobile guided tour | ![Mobile flow](assets/v6.1.7/mobile-flow.gif) |
| Workbench | ![Workbench](assets/v6.2.0/workbench-panel.png) |
| Squad Chat | ![Squad Chat](assets/v6.2.0/squad-chat.png) |
| Maintenance | ![Maintenance](assets/v6.2.0/maintenance-center.png) |
| Mobile board | ![Mobile board](assets/v6.2.0/mobile-board.png) |
| Mobile task | ![Mobile task workspace](assets/v6.2.0/mobile-task-workspace.png) |
| Mobile Settings | ![Mobile Settings](assets/v6.2.0/mobile-settings.png) |
| Mobile guided tour | ![Mobile flow](assets/v6.2.0/mobile-flow.gif) |
## Capture Rules
Run `pnpm docs:capture-media` to recreate the maintained 6.1.7 PNG and GIF set
from an isolated Playwright runtime. The capture uses public dummy tasks,
cleans them after the run, and builds the GIFs from the reviewed screenshots.
It requires `ffmpeg` on `PATH`.
Follow the [documentation media contract](design/DOCUMENTATION-MEDIA-CONTRACT.md).
Run `pnpm docs:capture-media <candidate.app> <new-external-directory>` from a
clean candidate checkout on macOS. The output directory must not already exist.
The runner launches the actual packaged application in an isolated profile,
captures the mobile browser separately, and records real interactions at 8 fps.
It requires `ffmpeg` and the pinned Playwright Chromium installation.
The original capture files and manifest remain outside the repository. After
visual inspection and complete GIF/MP4 playback, copy the exact bytes to the
versioned media paths and commit only documentation/media changes. Run
`pnpm docs:verify-media <capture/evidence.json> <candidate.app> <capture/publication.json>`
from that clean documentation commit. This verifies original, committed and
working-copy hashes without recapturing or relabeling an older build.
Before publication, retain only verified screenshots captured from an isolated
runtime without private data. If a state cannot be safely produced, retain the

Binary file not shown.

View file

@ -0,0 +1,39 @@
# 6.2.0 candidate media provenance
These are reviewed local-candidate captures, not evidence of a published or signed release.
- Build: `a2b8fbba0e2851d65611238624d8c9de347a2a27`; version: 6.2.0; bundle: `io.digitalmeld.veritas-kanban`.
- Whole-app SHA-256: `476a61cbc81f2a78b76657a87ccc98e2be645d2886d78e5df5fe51d7424dedcb`; macOS 26.6.2, Apple silicon, unsigned review package.
- Capture completed: 2026-09-08T00:35:55.632Z. Dark theme, reduced motion, isolated public-safe tasks. Native captures use the real package; mobile captures have no desktop bridge.
- Original JSON capture manifest: `media-public-620-4/evidence.json`, SHA-256 `bb92f226413b403639f0b6dcc5bbe9ef415fc1a4ea3302904bbd7f435de6d90d`. The original files, recording frames and manifest are retained outside public documentation under the [capture contract](../../design/DOCUMENTATION-MEDIA-CONTRACT.md).
- All 23 PNGs were individually inspected. The two new GIFs and retained baseline GIFs were played through complete cycles, and the MP4 reached its end without a playback error. Desktop and mobile recordings preserve the original input sequence: board, open task in Plan, select Plan, switch to Overview, close to board. Native captures omit the system cursor; visible focus rings remain. No synthetic cursor was added.
Desktop maintained captures use 1700×760 content dimensions; mobile browser views use 390×844. Task-mode views use an actual 1180×900 native window and measured content dimensions. The original task-mode audit includes a frame/shadow and uses a different blocked showcase task with failed history; this candidate uses a To Do documentation task with no run. The older maintained captures used macOS 15.7.9. The 5,000-task comparison uses the same synthetic generator and 1360×900 content dimensions on the current host. The original maintained board has a collapsed left rail; the candidate shows the current expanded default. These differences are disclosed in the [gallery](../../releases/v6.2.0-comparison.md).
| Asset | Capture boundary | Content dimensions | SHA-256 |
| -------------------------------------------------------- | ---------------- | ------------------ | ------------------------------------------------------------------ |
| [agent-providers.png](agent-providers.png) | packaged-macos | 1700×760 | `738dcb078ed9d04f392bdc61d388b677138412834261ecc07dce92352cb78990` |
| [board-overview.png](board-overview.png) | packaged-macos | 1700×760 | `04fdd6a0bd465ab6d05c60c51b3bed4deab1bbb10c4cbf5ce325c39ae9f1b338` |
| [board-to-workspace.gif](board-to-workspace.gif) | packaged-macos | 1700×760 | `ce440813e19cb05ea211407811622e75b7f835f3994a255990a5e815b1902bdc` |
| [command-palette.png](command-palette.png) | packaged-macos | 1700×760 | `db25eeca42d00a7b8056c99df58ea34822914f355833a0ac7db6debaedf5a700` |
| [maintenance-center.png](maintenance-center.png) | packaged-macos | 1700×760 | `b9a97426d7677917283d71c1055f2b87bb270770561beb0ce11c37e7065d65a7` |
| [mobile-board.png](mobile-board.png) | mobile-browser | 390×844 | `2f7be6ede22fc17da4d771e3e251ba620f126ee2f6b6f0cfc1b058b3e02fa7f0` |
| [mobile-flow.gif](mobile-flow.gif) | mobile-browser | 390×844 | `270017e341f9b03d1467b597391c5c1df0e40a0efbf3403f016608eb644ee163` |
| [mobile-settings.png](mobile-settings.png) | mobile-browser | 390×844 | `bb670a6ee3035beb37f8b143b1aabff81d5a51a2ba5767181383b842e7f26ccf` |
| [mobile-task-workspace.png](mobile-task-workspace.png) | mobile-browser | 390×844 | `7a55723b0ab0915c32d1e852d80a3dbe8c77026ef64336c947d019fcf2015b08` |
| [notification-adapters.png](notification-adapters.png) | packaged-macos | 1700×760 | `ff0ce568a5f7701a7ca63c0a0a54c1f61886f03443777e3bd17590f39793ae71` |
| [settings-navigation.png](settings-navigation.png) | packaged-macos | 1700×760 | `37061022142096d82227e692063687e157b62005fb622306e4b949bea351124f` |
| [squad-chat.png](squad-chat.png) | packaged-macos | 1700×760 | `06aa4e29fd285a2cab281a211843cd3ec1afd1561446f8903642d4c4bf1d91fe` |
| [task-workspace.png](task-workspace.png) | packaged-macos | 1700×760 | `022d92c0c1ef907fff22cb44a3c812c4ac54b5e2dfc99a3be4e73f43b3df9a32` |
| [workbench-panel.png](workbench-panel.png) | packaged-macos | 1700×760 | `b6f3de6615368e970d531e2666d324e94f8a1d1946fa633a6deb505d7c182334` |
| [task-drawer-overview.png](task-drawer-overview.png) | packaged-macos | 1180×900 | `c3792f8911f7313658421f75145729180148f771b47cfb63c646eda62f2df243` |
| [task-drawer-plan.png](task-drawer-plan.png) | packaged-macos | 1180×900 | `3879c43f95e90cfb40a3f56ef99478b15d2f25e0901f00c1a9092c4d0d967a4c` |
| [task-drawer-run.png](task-drawer-run.png) | packaged-macos | 1180×900 | `0eb3e06c6f8b0427e51ba76018028c1598dbd84efec54a48b413c1fb86948563` |
| [task-drawer-results.png](task-drawer-results.png) | packaged-macos | 1180×900 | `adfb2ed99bda4c5bdc36d585888e469770194d3de9b115b936b384b2b9d44027` |
| [task-drawer-history.png](task-drawer-history.png) | packaged-macos | 1180×900 | `ee445e7f5024928bce46451af5fc5cd86ab6e860cea7ee6cd8474139bb351169` |
| [task-expanded-overview.png](task-expanded-overview.png) | packaged-macos | 1180×900 | `faa98937bc33c7bd6581934e234b68b0b193e008ee29f801ad3cbc43656f4b22` |
| [task-expanded-plan.png](task-expanded-plan.png) | packaged-macos | 1180×900 | `0583ae2332857c7236fbe2ab6abf34a4f180462b419c639ee2f349eb1e0fc40d` |
| [task-expanded-run.png](task-expanded-run.png) | packaged-macos | 1180×900 | `1d2722305131bddb02c0c3a735714aac659149cdab525e1250548b7df2b2767f` |
| [task-expanded-results.png](task-expanded-results.png) | packaged-macos | 1180×900 | `e560f15b6e563ceebad24076ba872628e07c20ff6117d0781ead931c692c58cb` |
| [task-expanded-history.png](task-expanded-history.png) | packaged-macos | 1180×900 | `3ab524f5d0953b76c36c6443a9e126fc8fb86ba45d11c5ce5ee82c9bbbbeb8c8` |
| [board-5000.png](board-5000.png) | packaged-macos | 1360×900 | `2f1c3e9c230e5d14e19de61cc5121d5191faee0b7fe85d5a6d3cbfa676b4e3e4` |

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View file

@ -6,6 +6,8 @@ Release media must show the candidate that will ship. Keep historical version di
`scripts/docs-media/verify.mjs` owns the 14 maintained filenames. Every filename needs an explicit `keep`, `replace`, or `retire` decision and a reason. Both `keep` and `replace` require a current capture; `keep` is not permission to relabel old bytes. Retirement removes maintained references and cannot retire the required Board-to-workspace or mobile-flow interaction GIFs.
The runner also records ten `task-<drawer|expanded>-<overview|plan|run|results|history>.png` comparisons in the same versioned directory. These remain separate from the maintained inventory in `taskModeAssets`. If any is published, the complete ten-image capture manifest is required. Each has the same candidate and file-hash checks, an actual 1180×900 native window, and measured content dimensions. The manifest records fixture and framing differences: native content capture excludes the frame and shadow retained in the original 6.1.7 task-mode audit. Do not describe unmatched data or host conditions as identical.
Desktop media comes from the actual packaged macOS application. Mobile media uses the supported 390×844 browser viewport. Do not inject a desktop bridge and call a browser screenshot native evidence. GIFs must record the named interaction in progress and visibly complete it; a slideshow made from still screenshots does not qualify.
Use deterministic public-safe data. Inspect cropping, padding, focus, labels, active/hover state, theme accuracy, readable scale, and each GIF's playback. Check that maintained visuals contain no retired bottom chat dock, duplicate header controls, Advanced settings badges, or superseded task/template UI.
@ -32,4 +34,10 @@ Inspect the captured stills and play both GIFs and the demo MP4 through their co
From the clean documentation commit, run `pnpm docs:verify-media /absolute/path/capture/evidence.json /absolute/path/veritas-kanban.app /absolute/path/capture/publication.json`. The new publication manifest must stay beside its original capture manifest and files. Verification checks that each published asset and both demo-video copies match the original captured bytes, including Git's committed blobs; ignored or uncommitted files cannot stand in for published media. Supply `publication.json` to full release validation. This command performs no new captures and refuses to overwrite an existing publication manifest.
Permitted post-build changes are regular, non-executable README/documentation Markdown, `docs/index.json`, the demo player at `docs/demo/index.html`, the 14 versioned maintained media files, and the two demo MP4 paths. Source, dependency, build, version, executable, or other changes require a new clean build and fresh captures. A documentation-only commit cannot justify rewriting an older manifest, changing recorded bytes, or reusing a different app package. Historical preparation/recapture reports remain diagnostic and cannot be promoted to publication evidence by editing their mode or identity.
Permitted post-build changes are regular, non-executable README/documentation Markdown, `docs/index.json`, `docs/index.html`, the demo player at `docs/demo/index.html`, the 14 versioned maintained media files, the ten explicitly named task-mode comparison PNGs with their complete capture evidence, the optional verified `board-5000.png`, and the two demo MP4 paths. Source, dependency, build, version, executable, or other changes require a new clean build and fresh captures. A documentation-only commit cannot justify rewriting an older manifest, changing recorded bytes, or reusing a different app package. Historical preparation/recapture reports remain diagnostic and cannot be promoted to publication evidence by editing their mode or identity.
### Focused large-board comparison
The optional `focusedBoardAssets` capture list permits only `board-5000.png`, at 1360×900 native content pixels. It has the same build, app digest, timestamp, byte-hash and committed-file requirements as the maintained captures. A published focused image requires its original capture entry.
Run `node --import tsx ../scripts/native-ui/board-performance.mjs <candidate.app> <new-evidence-directory>` from `server/`. After that feature run passes, supply its `report.json` as the final argument to `pnpm docs:capture-media <candidate.app> <new-capture-directory> capture <board-report.json>`. The capture command verifies the same candidate identity and original screenshot hash before incorporating the focused image. This supplements the fourteen maintained assets and ten task-mode views; it does not replace them.

View file

@ -10,16 +10,16 @@ Squad Chat stores and streams messages. It does not wake an external agent proce
## Demo Screenshots
These release-safe captures use dummy content from the current 6.1.7 app
These release-safe captures use dummy content from the local 6.2.0 candidate
surfaces.
![Squad Chat coordination in Veritas Kanban 6.1.7](../assets/v6.1.7/squad-chat.png)
![Squad Chat coordination in Veritas Kanban 6.2.0](../assets/v6.2.0/squad-chat.png)
The Workbench view shows threaded agent coordination with search, unread count,
pinned decision state, acknowledgements, task/run chips, model attribution, a
human reply, and system lifecycle events.
![Notification and reply adapter settings in Veritas Kanban 6.1.7](../assets/v6.1.7/notification-adapters.png)
![Notification and reply adapter settings in Veritas Kanban 6.2.0](../assets/v6.2.0/notification-adapters.png)
The Notifications view shows local Squad Chat health, outbound webhook posture,
inbound wake/reply health, payload redaction, and the Microsoft Teams adapter

View file

@ -24,7 +24,7 @@
<meta property="og:url" content="https://github.com/BradGroux/veritas-kanban" />
<meta
property="og:image"
content="https://raw.githubusercontent.com/BradGroux/veritas-kanban/main/docs/assets/v6.1.7/board-overview.png"
content="https://raw.githubusercontent.com/BradGroux/veritas-kanban/main/docs/assets/v6.2.0/board-overview.png"
/>
<meta property="og:site_name" content="Veritas Kanban" />
@ -37,7 +37,7 @@
/>
<meta
name="twitter:image"
content="https://raw.githubusercontent.com/BradGroux/veritas-kanban/main/docs/assets/v6.1.7/board-overview.png"
content="https://raw.githubusercontent.com/BradGroux/veritas-kanban/main/docs/assets/v6.2.0/board-overview.png"
/>
<meta name="twitter:creator" content="@BradGroux" />

View file

@ -0,0 +1,219 @@
# Veritas Kanban 6.1.7 → 6.2.0 visual comparison
**Local review candidate; not a released build.** All original views have current equivalents. Every new still was inspected, both original and new GIFs were played through complete cycles, and the new MP4 played to completion without an error. The original images are verified against retained manifests.
The original 6.1.7 capture used build `ed5094c6a5f9dd6958ceb952d8d018a40135bf33`, bundle `io.digitalmeld.veritas-kanban`, and packaged-app SHA-256 `a23df6443940aff4d9b5013b97ab753b986a63f3bb15dc0d3a8c6aaace866f94`. All fourteen maintained assets match the recovered capture manifest, and all ten task-mode images match their [retained manifest](../assets/v6.1.7/task-mode-audit/manifest.json). Original files remain unchanged.
Desktop maintained captures use 1700×760 content dimensions; mobile browser captures use 390×844. The original task-mode audit used a 1180×900 native window and included its frame and shadow. The current capture records 1180×900 window content without the original frame/shadow. The original task-mode fixture, “Prototype AI triage,” was blocked with failed-run history; the new “Prepare the release candidate” fixture is To Do with no run. Differences in readiness, run results and history reflect those fixture states and do not establish a behavioral improvement. New captures use macOS 26.6.2; the original maintained captures used macOS 15.7.9.
The [media capture contract](../design/DOCUMENTATION-MEDIA-CONTRACT.md) binds new images to the actual packaged candidate and checks original and committed file hashes. Visual review and full GIF playback are separate acceptance steps.
Candidate build: `a2b8fbba0e2851d65611238624d8c9de347a2a27`; whole-app SHA-256: `476a61cbc81f2a78b76657a87ccc98e2be645d2886d78e5df5fe51d7424dedcb`. All new media comes from this candidate except the explicitly labeled mobile browser views.
## Maintained views
### Board overview
The candidate shows the expanded navigation rail with text labels; the retained baseline has the rail collapsed. The same five tasks occupy the same columns. This default-layout difference changes the card width and is not a performance comparison.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/board-overview.png)<br>![Board overview, 6.1.7](../assets/v6.1.7/board-overview.png) | [Open candidate](../assets/v6.2.0/board-overview.png)<br>![Board overview, 6.2.0](../assets/v6.2.0/board-overview.png) |
### Board to workspace
Both recordings use the same input sequence: board, open in Plan, select Plan, switch to Overview, close to board. Sampling is 8 fps with eight held frames after each completed stage; actual action duration can differ. The candidate uses the lighter task surface and background blur.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/board-to-workspace.gif)<br>![Board to workspace, 6.1.7](../assets/v6.1.7/board-to-workspace.gif) | [Open candidate](../assets/v6.2.0/board-to-workspace.gif)<br>![Board to workspace, 6.2.0](../assets/v6.2.0/board-to-workspace.gif) |
### Task workspace
The task remains in Plan with the same description and status. The candidate uses a lighter workspace surface and blurs the board behind it; detail loading and focus behavior were checked separately.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-workspace.png)<br>![Task workspace, 6.1.7](../assets/v6.1.7/task-workspace.png) | [Open candidate](../assets/v6.2.0/task-workspace.png)<br>![Task workspace, 6.2.0](../assets/v6.2.0/task-workspace.png) |
### Settings navigation
The baseline places Settings over the board. The candidate uses a separate modeless native window, adds search above the section content, and replaces the Dark mode switch with a System/Light/Dark appearance selector. Persistence was checked separately.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/settings-navigation.png)<br>![Settings navigation, 6.1.7](../assets/v6.1.7/settings-navigation.png) | [Open candidate](../assets/v6.2.0/settings-navigation.png)<br>![Settings navigation, 6.2.0](../assets/v6.2.0/settings-navigation.png) |
### Agent providers
The Agents controls remain in the same section within the new native Settings window. The wider surface exposes the provider list alongside persistent section navigation and search.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/agent-providers.png)<br>![Agent providers, 6.1.7](../assets/v6.1.7/agent-providers.png) | [Open candidate](../assets/v6.2.0/agent-providers.png)<br>![Agent providers, 6.2.0](../assets/v6.2.0/agent-providers.png) |
### Maintenance
Maintenance moves with Settings into its own native window. Storage and health values belong to each disposable capture profile; they are not a before/after operational measurement.
| 6.1.7 original | 6.2.0 candidate |
| -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/maintenance-center.png)<br>![Maintenance, 6.1.7](../assets/v6.1.7/maintenance-center.png) | [Open candidate](../assets/v6.2.0/maintenance-center.png)<br>![Maintenance, 6.2.0](../assets/v6.2.0/maintenance-center.png) |
### Notifications
Notifications remains grouped by health, adapters, delivery and preferences inside the separate Settings window. Fixture connections are disabled. Targeted notification storage updates were checked separately.
| 6.1.7 original | 6.2.0 candidate |
| ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/notification-adapters.png)<br>![Notifications, 6.1.7](../assets/v6.1.7/notification-adapters.png) | [Open candidate](../assets/v6.2.0/notification-adapters.png)<br>![Notifications, 6.2.0](../assets/v6.2.0/notification-adapters.png) |
### Command palette
The command list keeps its bounded palette and visible keyboard guidance. The board behind it reflects the different navigation-rail state; native command delivery was verified separately.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/command-palette.png)<br>![Command palette, 6.1.7](../assets/v6.1.7/command-palette.png) | [Open candidate](../assets/v6.2.0/command-palette.png)<br>![Command palette, 6.2.0](../assets/v6.2.0/command-palette.png) |
### Workbench
Board Chat remains in the right dock and preserves the task board beside it. The same public-safe empty conversation is shown; the candidate has expanded navigation labels.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/workbench-panel.png)<br>![Workbench, 6.1.7](../assets/v6.1.7/workbench-panel.png) | [Open candidate](../assets/v6.2.0/workbench-panel.png)<br>![Workbench, 6.2.0](../assets/v6.2.0/workbench-panel.png) |
### Squad Chat
Squad Chat remains selected inside the right dock, with the same empty conversation and Human sender. No external message was sent for this comparison.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/squad-chat.png)<br>![Squad Chat, 6.1.7](../assets/v6.1.7/squad-chat.png) | [Open candidate](../assets/v6.2.0/squad-chat.png)<br>![Squad Chat, 6.2.0](../assets/v6.2.0/squad-chat.png) |
### Mobile board
The dedicated mobile board keeps stacked columns, direct status controls and bottom navigation. This view does not show the separate compact filter controls, which were checked in the responsive board workflow.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/mobile-board.png)<br>![Mobile board, 6.1.7](../assets/v6.1.7/mobile-board.png) | [Open candidate](../assets/v6.2.0/mobile-board.png)<br>![Mobile board, 6.2.0](../assets/v6.2.0/mobile-board.png) |
### Mobile task flow
Both browser recordings open the same task in Plan, select Plan, switch to Overview and return to the board. The candidate updates the task surface while retaining the mobile mode selector.
| 6.1.7 original | 6.2.0 candidate |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| [Open original](../assets/v6.1.7/mobile-flow.gif)<br>![Mobile task flow, 6.1.7](../assets/v6.1.7/mobile-flow.gif) | [Open candidate](../assets/v6.2.0/mobile-flow.gif)<br>![Mobile task flow, 6.2.0](../assets/v6.2.0/mobile-flow.gif) |
### Mobile task workspace
At the same 390×844 browser viewport, Plan retains its mode and section selectors, description editor and status fields. The candidate changes the surface treatment without replacing the mobile navigation pattern.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/mobile-task-workspace.png)<br>![Mobile task workspace, 6.1.7](../assets/v6.1.7/mobile-task-workspace.png) | [Open candidate](../assets/v6.2.0/mobile-task-workspace.png)<br>![Mobile task workspace, 6.2.0](../assets/v6.2.0/mobile-task-workspace.png) |
### Mobile Settings
Browser Settings remains a dialog at 390×844. The candidate adds Settings search and the appearance selector; this is distinct from the modeless Mac Settings window.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/mobile-settings.png)<br>![Mobile Settings, 6.1.7](../assets/v6.1.7/mobile-settings.png) | [Open candidate](../assets/v6.2.0/mobile-settings.png)<br>![Mobile Settings, 6.2.0](../assets/v6.2.0/mobile-settings.png) |
## Every task workspace mode
The same task must stay mounted through Overview, Plan, Run, Results, and History, followed by the expanded presentation. The original task-mode fixture is separate from the maintained documentation fixture.
### Drawer: Overview
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| [Open original](../assets/v6.1.7/task-mode-audit/drawer-overview.png)<br>![Drawer: Overview, 6.1.7](../assets/v6.1.7/task-mode-audit/drawer-overview.png) | [Open candidate](../assets/v6.2.0/task-drawer-overview.png)<br>![Drawer: Overview, 6.2.0](../assets/v6.2.0/task-drawer-overview.png) |
### Drawer: Plan
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| [Open original](../assets/v6.1.7/task-mode-audit/drawer-plan.png)<br>![Drawer: Plan, 6.1.7](../assets/v6.1.7/task-mode-audit/drawer-plan.png) | [Open candidate](../assets/v6.2.0/task-drawer-plan.png)<br>![Drawer: Plan, 6.2.0](../assets/v6.2.0/task-drawer-plan.png) |
### Drawer: Run
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/drawer-run.png)<br>![Drawer: Run, 6.1.7](../assets/v6.1.7/task-mode-audit/drawer-run.png) | [Open candidate](../assets/v6.2.0/task-drawer-run.png)<br>![Drawer: Run, 6.2.0](../assets/v6.2.0/task-drawer-run.png) |
### Drawer: Results
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/drawer-results.png)<br>![Drawer: Results, 6.1.7](../assets/v6.1.7/task-mode-audit/drawer-results.png) | [Open candidate](../assets/v6.2.0/task-drawer-results.png)<br>![Drawer: Results, 6.2.0](../assets/v6.2.0/task-drawer-results.png) |
### Drawer: History
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/drawer-history.png)<br>![Drawer: History, 6.1.7](../assets/v6.1.7/task-mode-audit/drawer-history.png) | [Open candidate](../assets/v6.2.0/task-drawer-history.png)<br>![Drawer: History, 6.2.0](../assets/v6.2.0/task-drawer-history.png) |
### Expanded: Overview
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [Open original](../assets/v6.1.7/task-mode-audit/expanded-overview.png)<br>![Expanded: Overview, 6.1.7](../assets/v6.1.7/task-mode-audit/expanded-overview.png) | [Open candidate](../assets/v6.2.0/task-expanded-overview.png)<br>![Expanded: Overview, 6.2.0](../assets/v6.2.0/task-expanded-overview.png) |
### Expanded: Plan
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| [Open original](../assets/v6.1.7/task-mode-audit/expanded-plan.png)<br>![Expanded: Plan, 6.1.7](../assets/v6.1.7/task-mode-audit/expanded-plan.png) | [Open candidate](../assets/v6.2.0/task-expanded-plan.png)<br>![Expanded: Plan, 6.2.0](../assets/v6.2.0/task-expanded-plan.png) |
### Expanded: Run
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/expanded-run.png)<br>![Expanded: Run, 6.1.7](../assets/v6.1.7/task-mode-audit/expanded-run.png) | [Open candidate](../assets/v6.2.0/task-expanded-run.png)<br>![Expanded: Run, 6.2.0](../assets/v6.2.0/task-expanded-run.png) |
### Expanded: Results
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/expanded-results.png)<br>![Expanded: Results, 6.1.7](../assets/v6.1.7/task-mode-audit/expanded-results.png) | [Open candidate](../assets/v6.2.0/task-expanded-results.png)<br>![Expanded: Results, 6.2.0](../assets/v6.2.0/task-expanded-results.png) |
### Expanded: History
The active mode and task context remain visible in this presentation. Task content differs from the original showcase fixture; compare layout and navigation, not the displayed run outcome.
| 6.1.7 original | 6.2.0 candidate |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| [Open original](../assets/v6.1.7/task-mode-audit/expanded-history.png)<br>![Expanded: History, 6.1.7](../assets/v6.1.7/task-mode-audit/expanded-history.png) | [Open candidate](../assets/v6.2.0/task-expanded-history.png)<br>![Expanded: History, 6.2.0](../assets/v6.2.0/task-expanded-history.png) |
## Focused large-board comparison
### Board with 5,000 tasks
Both packages used the same synthetic task generator, 1360×900 content size, dark theme and this macOS 26.6.2 host. The candidate scrolls the selected offscreen task into view after keyboard movement; the baseline retained the first rows. Card truncation remains deliberate. The image shows selection and layout; performance and persistence claims come from the separately recorded packaged journey.
| 6.1.7 original | 6.2.0 candidate |
| --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Original focused capture is retained in the portable comparison evidence; the historical repository directory is unchanged. | ![Large board, 6.2.0](../assets/v6.2.0/board-5000.png) |

31
docs/releases/v6.2.0.md Normal file
View file

@ -0,0 +1,31 @@
Veritas Kanban 6.2.0 improves the everyday work of changing settings, navigating a board, and using the macOS application.
Draft release notes for an unpublished candidate. Packaging, acceptance and publication status are recorded separately in the release candidate evidence packet.
## What changed
Settings keeps pending edits when you change sections or close the window. Failed saves remain available for retry, and importing a settings file reports success after the server saves it. On macOS, Settings opens in one reusable native window, so the board stays usable alongside it. Quitting waits for pending settings saves and asks you to return to Settings if a save fails.
Settings search takes you to the relevant section and control. Appearance offers System, Light and Dark while retaining existing explicit preferences. On a phone, search and the active-filter count stay visible while secondary filters use a compact disclosure.
Native New Task and Settings commands reach the application. Standard macOS Close, Hide, Quit, zoom and full-screen commands are available. Window restoration accounts for changed displays, and title-bar double-click follows the system preference. Narrowing a window closes background chat without dismissing the task above it.
Keyboard navigation follows the visible board order and brings selected tasks into view. Shortcut help follows the configured columns. Modified commands and IME composition keep their normal behavior. Primary actions, selected filters and code labels have corrected contrast, and browser setup no longer presents unavailable desktop diagnostics.
Large boards load compact card records and window long columns. Full descriptions and run details load when you open a task; search still checks the complete description. Task reads revalidate after changes, and moving a task remains valid in columns containing more than 1,000 cards. Notification updates change the affected SQLite rows, while file-backed notification storage preserves recoverable data on write failures.
CLI and MCP commands reject empty or ambiguous task identifiers, enforce request deadlines, and preserve actionable API error information. Use a full task ID when a suffix matches more than one task.
## Compatibility and upgrade
The public REST API remains `v1`. All maintained packages must use 6.2.0 together. Existing full-task and summary APIs remain compatible, and this candidate adds no database migration. Retain a complete stopped-writer backup before an upgrade and keep it until the new runtime is accepted.
The macOS application requires macOS 13 Ventura or later on Apple silicon. Follow the [install and upgrade guide](../V6-UPGRADE-INSTALL-ADMIN-GUIDE.md) after the signed release becomes available. An unsigned local candidate is for verification and does not establish stable distribution readiness.
## Rollback
Restore the complete previous application bundle and, when needed, its matching stopped-writer backup. Do not mix bundled package versions. A System appearance preference is new in 6.2.0; select an explicit Light or Dark preference before returning to a version that does not support it.
See the [changelog](../../CHANGELOG.md) for user-visible changes and the [release candidate evidence packet](../V6-RC-EVIDENCE-PACKET.md) for the distinction between local verification, signing, installation and publication.
See the [before/after gallery](v6.2.0-comparison.md) for all retained views and the [capture provenance](../assets/v6.2.0/README.md) for the verified local build.

View file

@ -51,7 +51,7 @@ Candidate acceptance budgets, chosen before candidate measurements:
Run from the repository root with a clean build-bound candidate and a new output directory:
```bash
node --import tsx scripts/native-ui/board-performance.mjs /absolute/path/candidate.app /absolute/path/new-board-evidence
pnpm --dir server exec node --import tsx ../scripts/native-ui/board-performance.mjs /absolute/path/candidate.app /absolute/path/new-board-evidence
```
The runner uses only an isolated synthetic profile, stops its packaged server before seeding,

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/mcp",
"version": "6.1.7",
"version": "6.2.0",
"description": "MCP server for Veritas Kanban",
"type": "module",
"main": "./dist/index.js",

View file

@ -1,6 +1,6 @@
{
"name": "veritas-kanban",
"version": "6.1.7",
"version": "6.2.0",
"private": true,
"description": "Local-first task management and AI agent orchestration platform",
"author": "Brad Groux <brad@digitalmeld.io>",

View file

@ -4,7 +4,12 @@ import { createHash } from 'node:crypto';
import { readFile, realpath } from 'node:fs/promises';
import path from 'node:path';
import { fileDigest } from '../native-ui/contract.mjs';
import { maintainedAssets, verifyMediaEvidence } from './verify.mjs';
import {
maintainedAssets,
taskModeAssets,
focusedBoardAssets,
verifyMediaEvidence,
} from './verify.mjs';
export const publicationSchema = 'documentation-media-publication/v1';
const sha = (value) => typeof value === 'string' && /^[a-f0-9]{40}$/.test(value);
@ -24,7 +29,9 @@ function publicationPath(file, version) {
file === 'docs/index.html' ||
file === 'docs/demo/index.html' ||
['assets/demo-overview.mp4', 'docs/assets/demo-overview.mp4'].includes(file) ||
maintainedAssets.some((name) => file === `docs/assets/v${version}/${name}`)
[...maintainedAssets, ...taskModeAssets, ...focusedBoardAssets].some(
(name) => file === `docs/assets/v${version}/${name}`
)
);
}
@ -158,7 +165,42 @@ export async function verifyPublishedMedia({
maintainedContents,
});
if (errors.length) return errors;
for (const asset of capture.assets) {
const committedTaskModes = taskModeAssets.filter((name) =>
git(
root,
'ls-tree',
expected.publicationCommit,
'--',
`docs/assets/v${expected.version}/${name}`
).trim()
);
if (committedTaskModes.length)
assert.deepEqual(
(capture.taskModeAssets ?? []).map((asset) => asset.name).sort(),
[...taskModeAssets].sort(),
'Published task mode media requires the complete original capture manifest'
);
for (const name of focusedBoardAssets) {
if (
git(
root,
'ls-tree',
expected.publicationCommit,
'--',
`docs/assets/v${expected.version}/${name}`
).trim()
)
assert.equal(
capture.focusedBoardAssets?.[0]?.name,
name,
'Published focused board media requires original capture evidence'
);
}
for (const asset of [
...capture.assets,
...(capture.taskModeAssets ?? []),
...(capture.focusedBoardAssets ?? []),
]) {
if (asset.decision === 'retire') continue;
verifyCommittedFile(root, expected.publicationCommit, asset.path, asset.sha256);
assert.equal(

View file

@ -253,3 +253,36 @@ test('a symlink cannot substitute an external original capture', async (t) => {
await symlink(path.join(f.root, 'docs/assets/v6.1.7/board-overview.png'), file);
assert((await verifyPublishedMedia(f.args)).some((error) => error.includes('symlink')));
});
test('task mode images cannot enter publication without their original capture manifest', async (t) => {
const f = await fixture(t);
await writeFile(
path.join(f.root, 'docs/assets/v6.1.7/task-drawer-overview.png'),
'unrecorded image'
);
f.git('add', '.');
f.git('commit', '-qm', 'add unrecorded task view');
f.expected.publicationCommit = f.git('rev-parse', 'HEAD');
f.publication.publicationCommit = f.expected.publicationCommit;
await writeFile(f.evidencePath, JSON.stringify(f.publication));
assert(
(await verifyPublishedMedia(f.args)).some((error) =>
error.includes('complete original capture manifest')
)
);
});
test('focused board media cannot be published without original capture evidence', async (t) => {
const f = await fixture(t);
await writeFile(path.join(f.root, 'docs/assets/v6.1.7/board-5000.png'), 'unrecorded image');
f.git('add', '.');
f.git('commit', '-qm', 'add unrecorded board');
f.expected.publicationCommit = f.git('rev-parse', 'HEAD');
f.publication.publicationCommit = f.expected.publicationCommit;
await writeFile(f.evidencePath, JSON.stringify(f.publication));
assert(
(await verifyPublishedMedia(f.args)).some((error) =>
error.includes('original capture evidence')
)
);
});

View file

@ -2,22 +2,22 @@
/* global window, document, innerWidth, innerHeight, getComputedStyle, requestAnimationFrame */
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
import { copyFile, mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
import { chromium, expect } from '@playwright/test';
import { nativeSettingsPage, closeNativeSettings } from '../native-ui/settings-window.mjs';
import { createNativeSession } from '../native-ui/session.mjs';
import { contentSizes, fileDigest, packageDigest } from '../native-ui/contract.mjs';
import { maintainedAssets, mediaSchema, mediaEvidenceFailures } from './verify.mjs';
import { maintainedAssets, taskModeAssets, mediaSchema, mediaEvidenceFailures } from './verify.mjs';
import { encodeInteraction, recordInteraction } from './record.mjs';
import { finalizeCapture } from './finalize.mjs';
const root = path.resolve(import.meta.dirname, '../..');
const [appArgument, outputArgument, mode = 'capture'] = process.argv.slice(2);
const [appArgument, outputArgument, mode = 'capture', focusedBoardArgument] = process.argv.slice(2);
assert(
appArgument?.endsWith('.app') && outputArgument && mode === 'capture',
'Usage: node scripts/docs-media/run.mjs <candidate.app> <new-external-directory> [capture]'
'Usage: node scripts/docs-media/run.mjs <candidate.app> <new-external-directory> [capture] [board-report.json]'
);
assert.equal(process.platform, 'darwin', 'Desktop documentation captures require macOS');
const output = path.join(
@ -50,6 +50,26 @@ const report = {
startedAt: new Date().toISOString(),
macOS: os.release(),
assets: [],
fixture: {
source: 'scripts/docs-media/run.mjs',
kind: 'synthetic-public-safe',
taskTitle: 'Prepare the release candidate',
status: 'todo',
},
taskModeComparison: {
baseline: 'docs/assets/v6.1.7/task-mode-audit/manifest.json',
matched: [
'native window 1180x900',
'dark theme',
'Overview/Plan/Run/Results/History',
'drawer then expanded',
],
unmatched: [
'Original showcase dataset is unavailable; uses the documentation fixture.',
'Native content capture excludes the baseline window frame and shadow.',
'Host OS differs from the baseline macOS 15.7.9.',
],
},
};
const persist = () =>
writeFile(path.join(output, 'evidence.json'), JSON.stringify(report, null, 2) + '\n');
@ -101,7 +121,7 @@ async function settle() {
() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
);
}
async function capture() {
async function capture(taskMode = false) {
if (mobile) {
assert.deepEqual(await page.evaluate(() => [innerWidth, innerHeight]), [390, 844]);
assert.equal(await page.evaluate(() => typeof window.veritasDesktop), 'undefined');
@ -112,6 +132,9 @@ async function capture() {
scaleFactor: 1,
};
}
// DOM readiness and animation frames can precede Electron's last composited frame.
// Synchronize the renderer first; the published bytes still come from capturePage.
await page.screenshot({ fullPage: false });
const native = await app.evaluate(async ({ BrowserWindow, screen }, url) => {
const win = BrowserWindow.getAllWindows().find((w) => w.webContents.getURL() === url);
return {
@ -121,19 +144,28 @@ async function capture() {
png: (await win.capturePage()).toPNG().toString('base64'),
};
}, page.url());
assert.equal(native.contentBounds.width, contentSizes.normal.width);
assert.equal(native.contentBounds.height, contentSizes.normal.height);
if (taskMode) {
assert.equal(native.bounds.width, 1180);
assert.equal(native.bounds.height, 900);
assert.equal(native.contentBounds.width, 1180);
assert(native.contentBounds.height >= 760 && native.contentBounds.height <= 900);
} else {
assert.equal(native.contentBounds.width, contentSizes.normal.width);
assert.equal(native.contentBounds.height, contentSizes.normal.height);
}
return {
bytes: Buffer.from(native.png, 'base64'),
width: contentSizes.normal.width,
height: contentSizes.normal.height,
width: native.contentBounds.width,
height: native.contentBounds.height,
scaleFactor: native.scaleFactor,
nativeWindow: { bounds: native.bounds, contentBounds: native.contentBounds },
};
}
async function recordAsset(name, captured, method, recording) {
const file = path.join(output, name);
report.assets.push({
const taskMode = taskModeAssets.includes(name);
const destination = taskMode ? (report.taskModeAssets ??= []) : report.assets;
destination.push({
name,
decision: 'replace',
reason: 'Recaptured the converged interface from the candidate',
@ -149,6 +181,7 @@ async function recordAsset(name, captured, method, recording) {
height: captured.height,
scaleFactor: captured.scaleFactor,
nativeWindow: captured.nativeWindow,
...(taskMode ? { framing: 'native-content-without-window-frame' } : {}),
method,
capturedAt: new Date().toISOString(),
},
@ -159,7 +192,7 @@ async function recordAsset(name, captured, method, recording) {
}
async function still(name) {
await settle();
const captured = await capture();
const captured = await capture(taskModeAssets.includes(name));
await writeFile(path.join(output, name), captured.bytes, { flag: 'wx' });
await recordAsset(name, captured, 'window-capture');
}
@ -350,8 +383,58 @@ try {
await still('mobile-task-workspace.png');
await button('Close task workspace').click();
await button('Mobile settings').click();
await expect(page.getByRole('dialog', { name: /^Settings(?: Board Only)?$/ })).toBeVisible();
const mobileSettings = page.getByRole('dialog', { name: /^Settings(?: Board Only)?$/ });
await expect(mobileSettings).toBeVisible();
await expect(mobileSettings.getByRole('heading', { name: 'General', exact: true })).toBeVisible();
await expect(mobileSettings.getByRole('combobox', { name: 'Appearance', exact: true })).toBeVisible();
await still('mobile-settings.png');
page = boardPage;
mobile = false;
await openTask();
// Match the retained task-mode audit's native window dimensions. Native
// capturePage excludes its frame/shadow; that framing difference is recorded.
await app.evaluate(({ BrowserWindow }, url) => {
const win = BrowserWindow.getAllWindows().find((w) => w.webContents.getURL() === url);
win.setSize(1180, 900);
}, page.url());
for (const presentation of ['drawer', 'expanded']) {
if (presentation === 'expanded') await button('Expand task workspace').click();
for (const name of ['Overview', 'Plan', 'Run', 'Results', 'History']) {
await workspaceMode(name);
await still(`task-${presentation}-${name.toLowerCase()}.png`);
}
}
await app.evaluate(({ BrowserWindow }, url) => {
const win = BrowserWindow.getAllWindows().find((w) => w.webContents.getURL() === url);
win.setContentSize(1700, 760);
}, page.url());
await button('Close task workspace').click();
if (focusedBoardArgument) {
const boardReportPath = await realpath(focusedBoardArgument);
const board = JSON.parse(await readFile(boardReportPath, 'utf8'));
assert.equal(board.status, 'passed', 'Focused board capture requires a passing feature run');
for (const key of ['commit', 'version', 'packageDigest'])
assert.equal(board[key], report[key], `Focused board capture has a different ${key}`);
const entry = board.entries?.find((item) => item.count === 5000);
assert.deepEqual(entry?.failures, [], 'Focused board capture missed a performance budget');
assert.equal(entry?.screenshot?.name, 'board-5000.png');
const source = path.join(path.dirname(boardReportPath), entry.screenshot.name);
assert.equal(await realpath(source), source, 'Focused capture traverses a symlink');
assert.equal(
await fileDigest(source),
entry.screenshot.sha256,
'Focused capture bytes changed'
);
await copyFile(source, path.join(output, entry.screenshot.name));
report.focusedBoardAssets = [
{
...entry.screenshot,
decision: 'replace',
reason: 'Focused comparison of the matched 5000-task board fixture.',
path: `docs/assets/v${version}/${entry.screenshot.name}`,
},
];
}
report.assets.sort((a, b) => maintainedAssets.indexOf(a.name) - maintainedAssets.indexOf(b.name));
report.completedAt = new Date().toISOString();
report.status = 'captured';

View file

@ -20,6 +20,13 @@ export const maintainedAssets = [
'workbench-panel.png',
];
export const taskModeAssets = ['drawer', 'expanded'].flatMap((presentation) =>
['overview', 'plan', 'run', 'results', 'history'].map(
(mode) => `task-${presentation}-${mode}.png`
)
);
export const focusedBoardAssets = ['board-5000.png'];
const digest = (value) => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
const text = (value) => typeof value === 'string' && value.trim().length > 0;
@ -75,14 +82,38 @@ export function mediaEvidenceFailures(report, expected, now = Date.now()) {
new Set(assets.map((asset) => asset?.name)).size !== maintainedAssets.length
)
errors.push('missing or duplicate maintained media decisions');
for (const name of maintainedAssets) {
const asset = assets.find((item) => item?.name === name);
const supplemental = report.taskModeAssets ?? [];
const focused = report.focusedBoardAssets ?? [];
if (!Array.isArray(focused)) return [...errors, 'invalid focused board capture list'];
if (
report.focusedBoardAssets !== undefined &&
(focused.length !== 1 || focused[0]?.name !== focusedBoardAssets[0])
)
errors.push('missing or duplicate focused board capture');
if (!Array.isArray(supplemental)) return [...errors, 'invalid task mode capture list'];
if (
report.taskModeAssets !== undefined &&
(supplemental.length !== taskModeAssets.length ||
new Set(supplemental.map((asset) => asset?.name)).size !== taskModeAssets.length)
)
errors.push('missing or duplicate task mode captures');
const expectedNames = [
...maintainedAssets,
...(report.taskModeAssets === undefined ? [] : taskModeAssets),
...(report.focusedBoardAssets === undefined ? [] : focusedBoardAssets),
];
for (const name of expectedNames) {
const asset = [...assets, ...supplemental, ...focused].find((item) => item?.name === name);
if (!asset || !['keep', 'replace', 'retire'].includes(asset.decision) || !text(asset.reason)) {
errors.push(`${name}: missing explicit media decision and reason`);
continue;
}
if (asset.decision === 'retire') {
if (name.endsWith('.gif'))
if (
name.endsWith('.gif') ||
taskModeAssets.includes(name) ||
focusedBoardAssets.includes(name)
)
errors.push(`${name}: named interaction GIF must remain maintained`);
if (asset.path !== undefined || asset.sha256 !== undefined || asset.capture !== undefined)
errors.push(`${name}: retired media must not claim a current capture`);
@ -100,9 +131,25 @@ export function mediaEvidenceFailures(report, expected, now = Date.now()) {
if (capture?.boundary !== boundary) errors.push(`${name}: wrong capture boundary`);
if (boundary === 'packaged-macos' && capture?.packaged !== true)
errors.push(`${name}: desktop capture did not use the packaged application`);
const viewport =
boundary === 'mobile-browser' ? { width: 390, height: 844 } : contentSizes.normal;
if (capture?.width !== viewport.width || capture?.height !== viewport.height)
const viewport = focusedBoardAssets.includes(name)
? { width: 1360, height: 900 }
: boundary === 'mobile-browser'
? { width: 390, height: 844 }
: contentSizes.normal;
const taskMode = taskModeAssets.includes(name);
if (
taskMode &&
(capture?.nativeWindow?.bounds?.width !== 1180 ||
capture?.nativeWindow?.bounds?.height !== 900 ||
capture?.width !== 1180 ||
capture?.height < 760 ||
capture?.height > 900 ||
capture?.nativeWindow?.contentBounds?.width !== capture?.width ||
capture?.nativeWindow?.contentBounds?.height !== capture?.height ||
capture?.framing !== 'native-content-without-window-frame')
)
errors.push(`${name}: missing matched task window geometry or explicit framing`);
if (!taskMode && (capture?.width !== viewport.width || capture?.height !== viewport.height))
errors.push(
`${name}: wrong supported ${boundary === 'mobile-browser' ? 'mobile' : 'packaged macOS'} viewport`
);
@ -172,7 +219,11 @@ export async function verifyMediaEvidence({ evidencePath, root, expected, mainta
} catch (error) {
return [`missing candidate media directory: ${error.message}`];
}
for (const asset of report.assets) {
for (const asset of [
...report.assets,
...(report.taskModeAssets ?? []),
...(report.focusedBoardAssets ?? []),
]) {
if (asset.decision === 'retire') continue;
try {
const intendedFile = path.join(root, asset.path);

View file

@ -1,3 +1,4 @@
/* global structuredClone */
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { mkdtemp, mkdir, rm, symlink, writeFile } from 'node:fs/promises';
@ -6,6 +7,7 @@ import path from 'node:path';
import test from 'node:test';
import {
maintainedAssets,
taskModeAssets,
mediaSchema,
mediaEvidenceFailures,
staleMediaReferences,
@ -223,3 +225,58 @@ test('missing maintained reference inventory cannot silently skip stale-referenc
'missing maintained documentation reference inventory',
]);
});
test('task mode comparison binds every view to native geometry and candidate identity', () => {
const report = fixture();
report.taskModeAssets = taskModeAssets.map((name) => ({
...structuredClone(report.assets[0]),
name,
path: `docs/assets/v6.1.7/${name}`,
capture: {
...report.assets[0].capture,
width: 1180,
height: 872,
nativeWindow: {
bounds: { width: 1180, height: 900 },
contentBounds: { width: 1180, height: 872 },
},
framing: 'native-content-without-window-frame',
},
}));
assert.deepEqual(mediaEvidenceFailures(report, expected), []);
for (const mutate of [
(r) => r.taskModeAssets.pop(),
(r) => (r.taskModeAssets[0].capture.commit = 'c'.repeat(40)),
(r) => (r.taskModeAssets[0].capture.nativeWindow.bounds.height = 760),
(r) => delete r.taskModeAssets[0].capture.framing,
(r) => (r.taskModeAssets[0].decision = 'retire'),
(r) => (r.taskModeAssets[0].path = 'docs/assets/v6.1.7/../escape.png'),
]) {
const changed = structuredClone(report);
mutate(changed);
assert(mediaEvidenceFailures(changed, expected).length);
}
});
test('focused large-board capture requires the exact native viewport and build', () => {
const report = fixture();
report.focusedBoardAssets = [
{
...structuredClone(report.assets[0]),
name: 'board-5000.png',
path: 'docs/assets/v6.1.7/board-5000.png',
capture: { ...report.assets[0].capture, width: 1360, height: 900 },
},
];
assert.deepEqual(mediaEvidenceFailures(report, expected), []);
for (const mutate of [
(r) => r.focusedBoardAssets.push(r.focusedBoardAssets[0]),
(r) => (r.focusedBoardAssets[0].capture.packageDigest = 'c'.repeat(64)),
(r) => (r.focusedBoardAssets[0].capture.width = 1700),
(r) => (r.focusedBoardAssets[0].decision = 'retire'),
]) {
const changed = structuredClone(report);
mutate(changed);
assert(mediaEvidenceFailures(changed, expected).length);
}
});

View file

@ -6,6 +6,7 @@ import path from 'node:path';
import { mkdir, writeFile, readFile, realpath } from 'node:fs/promises';
import { pathToFileURL } from 'node:url';
import { performance } from 'node:perf_hooks';
import { verifyLargeBoardJourney } from './large-board-journey.mjs';
const root = path.resolve(import.meta.dirname, '../..');
const { createNativeSession } = await import(
pathToFileURL(`${root}/scripts/native-ui/session.mjs`)
@ -16,7 +17,9 @@ const { SqliteDatabase } = await import(
const { SqliteTaskRepository } = await import(
pathToFileURL(`${root}/server/src/storage/sqlite/task-repository.ts`)
);
const { packageDigest } = await import(pathToFileURL(`${root}/scripts/native-ui/contract.mjs`));
const { packageDigest, fileDigest } = await import(
pathToFileURL(`${root}/scripts/native-ui/contract.mjs`)
);
const require = createRequire(`${root}/package.json`);
const { expect } = require('@playwright/test');
assert(
@ -174,6 +177,24 @@ try {
})
);
await page.screenshot({ path: `${output}/board-${count}.png` });
entry.screenshot = {
name: `board-${count}.png`,
sha256: await fileDigest(`${output}/board-${count}.png`),
capture: {
commit,
version,
packageDigest: report.packageDigest,
boundary: 'packaged-macos',
packaged: true,
method: 'window-capture',
capturedAt: new Date().toISOString(),
...(await page.evaluate(() => ({
width: window.innerWidth,
height: window.innerHeight,
scaleFactor: window.devicePixelRatio,
}))),
},
};
entry.budgets = {
loadRenderMs: count === 100 ? 1000 : count === 1000 ? 2000 : 5000,
inputP95Ms: 100,
@ -190,6 +211,10 @@ try {
if (responses.some((response) => !response.query.includes('view=board')))
entry.failures.push('Initial board requested full task records');
await persist();
if (count === 5000) {
report.journey = await verifyLargeBoardJourney(page, count);
await persist();
}
}
report.status = report.entries.some((entry) => entry.failures.length) ? 'failed' : 'passed';
if (report.status === 'failed') process.exitCode = 1;

View file

@ -73,21 +73,18 @@ export const requiredEntries = modes.flatMap((mode) =>
);
export function requiresOverlay(id) {
const state = id.split('/')[1];
return (
state?.startsWith('settings-') ||
[
'task-chat',
'task-drawer',
'task-expanded',
'create-task',
'create-template',
'edit-template',
'search',
'command-palette',
'preview',
'confirmation',
].includes(state)
);
return [
'task-chat',
'task-drawer',
'task-expanded',
'create-task',
'create-template',
'edit-template',
'search',
'command-palette',
'preview',
'confirmation',
].includes(state);
}
// Expected production parts are keyed by the exercised surface, not whatever CSS
@ -96,7 +93,6 @@ export function requiredOverlayParts(id) {
const state = id.split('/')[1];
if (['task-chat', 'task-drawer', 'task-expanded'].includes(state))
return ['task-header', 'task-body'];
if (state?.startsWith('settings-')) return ['header', 'body', 'scroll'];
if (state === 'search') return ['header', 'body'];
if (requiresOverlay(id) || ['clipped-modal', 'overlay-padding'].includes(state))
return ['header', 'body', 'scroll', 'footer'];
@ -325,6 +321,15 @@ export function evidenceFailures(report, expected, now = Date.now()) {
!report.identity?.osVersion
)
errors.push(`${id}: missing native environment`);
if (
id.split('/')[1]?.startsWith('settings-') &&
(entry.windowRole !== 'settings' ||
native?.role !== 'settings' ||
native?.visible !== true ||
native?.modal !== false ||
native?.settingsWindowCount !== 1)
)
errors.push(`${id}: missing modeless native Settings window`);
if (requiresOverlay(id) && !entry.geometry?.overlays?.length)
errors.push(`${id}: missing required overlay`);
for (const overlay of entry.geometry?.overlays ?? []) {

View file

@ -76,7 +76,12 @@ function report() {
status: 'passed',
theme: mode.theme,
screenshot: { path: `${id}.png`, sha256: digest },
windowRole: id.split('/')[1]?.startsWith('settings-') ? 'settings' : 'board',
nativeWindow: {
role: id.split('/')[1]?.startsWith('settings-') ? 'settings' : 'board',
visible: true,
modal: false,
settingsWindowCount: 1,
bounds: { x: 0, y: 0, width: mode.width, height: mode.height },
contentBounds: { x: 0, y: 0, width: mode.width, height: mode.height },
scaleFactor: 2,
@ -368,7 +373,6 @@ test('required footer and compound scroll measurements cannot disappear', () =>
for (const [state, kind] of [
['confirmation', 'footer'],
['create-task', 'scroll'],
['settings-general', 'scroll'],
]) {
const r = report();
const overlay = r.entries.find((entry) => entry.id === `light-normal/${state}`).geometry
@ -383,6 +387,11 @@ test('required footer and compound scroll measurements cannot disappear', () =>
});
test('missing overlay measurements and invalid native bounds fail closed', () => {
for (const mutate of [
(r) => delete r.entries.find((e) => e.id.endsWith('/settings-general')).nativeWindow.role,
(r) => (r.entries.find((e) => e.id.endsWith('/settings-general')).nativeWindow.modal = true),
(r) =>
(r.entries.find((e) => e.id.endsWith('/settings-general')).nativeWindow.settingsWindowCount =
2),
(r) => (r.entries.find((e) => requiresOverlay(e.id)).geometry.overlays = []),
(r) => (r.entries.find((e) => requiresOverlay(e.id)).geometry.overlays = [{ parts: [] }]),
(r) => (r.entries[0].nativeWindow.bounds = {}),

View file

@ -0,0 +1,120 @@
import assert from 'node:assert/strict';
import { expect } from '@playwright/test';
/** Exercise the real packaged board after the performance sample is recorded. */
export async function verifyLargeBoardJourney(page, count) {
const column = (status) => page.locator(`[data-column-status="${status}"]`);
const card = (id) => page.locator(`[data-task-id="${id}"][role="article"]`);
const total = () =>
page
.locator('.vk-column-status-count')
.evaluateAll((elements) =>
elements.reduce((sum, element) => sum + Number(element.textContent), 0)
);
const request = (method, route, body) =>
page.evaluate(
async ({ method, route, body }) => {
const response = await fetch(route, {
method,
headers: { 'Content-Type': 'application/json' },
...(body ? { body: JSON.stringify(body) } : {}),
});
if (!response.ok) throw new Error(`Fixture request failed: ${response.status}`);
const payload = await response.json();
return payload.data ?? payload;
},
{ method, route, body }
);
await column('todo').locator('h2').click();
await page.keyboard.press('Escape');
// More than one rendered window: selection must scroll and remain mounted.
for (let index = 0; index < 65; index++) await page.keyboard.press('j');
const id = 'task_scale_00256';
await expect(card(id)).toHaveAttribute('data-selected', 'true');
await expect(card(id)).toBeInViewport();
const detailResponse = page.waitForResponse(
(response) =>
new URL(response.url()).pathname === `/api/tasks/${id}` &&
response.request().method() === 'GET'
);
await page.keyboard.press('Enter');
const detail = await detailResponse;
assert(detail.ok(), 'Full task detail failed after selecting an offscreen summary');
const payload = await detail.json();
assert.equal((payload.data ?? payload).description.length, 4096);
const panel = page.getByTestId('task-detail-panel');
await expect(panel).toBeVisible();
await expect(panel.getByRole('textbox', { name: 'Task title', exact: true })).toHaveValue(
'Scale fixture 00256'
);
await page.getByRole('button', { name: 'Close task workspace', exact: true }).click();
await expect(panel).toBeHidden();
const moved = page.waitForResponse(
(response) =>
new URL(response.url()).pathname === `/api/tasks/${id}/move` &&
response.request().method() === 'POST'
);
await page.keyboard.press('3');
assert((await moved).ok(), 'Keyboard status move failed');
await expect(column('blocked').locator(`[data-task-id="${id}"]`)).toBeInViewport();
assert.equal((await request('GET', `/api/tasks/${id}`)).status, 'blocked');
const search = page.getByRole('textbox', { name: 'Search tasks', exact: true });
// This text lives only in the full description, not the board summary.
const descriptionResponse = page.waitForResponse((response) => {
const url = new URL(response.url());
return (
url.pathname === '/api/tasks' &&
url.searchParams.get('fields') === 'id' &&
url.searchParams.get('search') === 'Public-safe board performance fixture'
);
});
await search.fill('Public-safe board performance fixture');
const searchResponse = await descriptionResponse;
assert(searchResponse.ok(), 'Full-description search failed');
const searchPayload = await searchResponse.json();
assert.equal((searchPayload.data ?? searchPayload).length, count);
await expect.poll(total).toBe(count);
await search.fill('Scale fixture 00256');
await expect.poll(total).toBe(1);
await expect(card(id)).toBeVisible();
// Direct API mutation performs no React Query invalidation in this renderer.
// The changed card must arrive through the production realtime path.
const title = 'Scale fixture 00256 realtime update';
await request('PATCH', `/api/tasks/${id}`, { title });
await expect(card(id).getByRole('heading', { name: title, exact: true })).toBeVisible();
const source = await card(id).boundingBox();
const target = await column('todo').boundingBox();
assert(source && target, 'Drag source or destination has no visible bounds');
const dragged = page.waitForResponse(
(response) =>
new URL(response.url()).pathname === `/api/tasks/${id}/move` &&
response.request().method() === 'POST'
);
await page.mouse.move(source.x + source.width / 2, source.y + 30);
await page.mouse.down();
await page.mouse.move(source.x + source.width / 2 - 15, source.y + 35, { steps: 3 });
await page.mouse.move(target.x + target.width / 2, target.y + 100, { steps: 20 });
await page.mouse.up();
assert((await dragged).ok(), 'Pointer drag did not commit');
await expect(column('todo').locator(`[data-task-id="${id}"]`)).toBeVisible();
assert.equal((await request('GET', `/api/tasks/${id}`)).status, 'todo');
await search.fill('');
await expect.poll(total).toBe(count);
await page.reload();
await expect.poll(total).toBe(count);
assert.equal((await request('GET', `/api/tasks/${id}`)).title, title);
return {
offscreenKeyboardSelection: true,
fullDetail: true,
keyboardMove: true,
descriptionSearch: true,
realtimeUpdate: true,
pointerMove: true,
persistedAfterReload: true,
};
}

View file

@ -206,6 +206,13 @@ async function capture(entry) {
const native = await app.evaluate(async ({ BrowserWindow, screen }, url) => {
const window = BrowserWindow.getAllWindows().find((w) => w.webContents.getURL() === url);
return {
role: new URL(url).searchParams.get('desktop-settings') === '1' ? 'settings' : 'board',
visible: window.isVisible(),
modal: window.isModal(),
settingsWindowCount: BrowserWindow.getAllWindows().filter(
(candidate) =>
new URL(candidate.webContents.getURL()).searchParams.get('desktop-settings') === '1'
).length,
bounds: window.getBounds(),
contentBounds: window.getContentBounds(),
minimumSize: window.getMinimumSize(),

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/server",
"version": "6.1.7",
"version": "6.2.0",
"private": true,
"type": "module",
"main": "./dist/index.js",

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/shared",
"version": "6.1.7",
"version": "6.2.0",
"private": true,
"type": "module",
"main": "./dist/index.js",

View file

@ -1,6 +1,6 @@
{
"name": "@veritas-kanban/web",
"version": "6.1.7",
"version": "6.2.0",
"private": true,
"type": "module",
"scripts": {

View file

@ -46,7 +46,9 @@ function createFeatureSettings(
}
vi.mock('@/hooks/useTasks', () => ({
useTasks: () => mockUseTasks(),
useBoardTasks: () => mockUseTasks(),
useBoardSearch: () => ({ data: [], isFetching: false, error: null, refetch: vi.fn() }),
useTask: (id: string) => ({ data: mockUseTasks().data?.find((task: Task) => task.id === id) }),
useTasksByStatus: (tasks: Task[], columns?: Array<{ id: string }>) => {
const result: Record<string, Task[]> = Object.fromEntries(
(columns ?? [{ id: 'todo' }, { id: 'in-progress' }, { id: 'blocked' }, { id: 'done' }]).map(

View file

@ -19,7 +19,7 @@ vi.mock('@/hooks/useTasks', async () => {
const actual = await vi.importActual<typeof import('@/hooks/useTasks')>('@/hooks/useTasks');
return {
...actual,
useTasks: mocks.useTasks,
useBoardTasks: mocks.useTasks,
useAddVerificationStep: () => ({ mutateAsync: mocks.addVerificationStep }),
useUpdateVerificationStep: () => ({ mutateAsync: mocks.updateVerificationStep }),
useDeleteVerificationStep: () => ({ mutateAsync: mocks.deleteVerificationStep }),