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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: Product Acceptance

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: acceptance-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: true

env:
RIVET_PIN: b1d05950537f8679b2cfb48aded383c5162c288f
KEEL_PIN: 0740fdfbdab7bf5dab32232c82f07dd98aa6a196

jobs:
windows:
name: Windows packaged WinUI acceptance
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- uses: Bogdanp/setup-racket@v1.15
with:
architecture: x64
distribution: full
variant: CS
version: '9.3'
- name: Install pinned dependencies
shell: pwsh
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/keel.git#$env:KEEL_PIN"
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#$env:RIVET_PIN"
- name: Run executable acceptance contract
shell: pwsh
run: |
New-Item -ItemType Directory -Force .rivet\acceptance | Out-Null
$report = & raco rivet acceptance --json
$status = $LASTEXITCODE
$report | Tee-Object -FilePath .rivet\acceptance\report.json
exit $status
- name: Upload Windows evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: taskly-acceptance-windows
path: .rivet/acceptance
if-no-files-found: warn

macos:
name: macOS packaged SwiftUI acceptance (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- { arch: arm64, runner: macos-15, racket-arch: arm64 }
- { arch: x64, runner: macos-15-intel, racket-arch: x64 }
steps:
- uses: actions/checkout@v4
- uses: Bogdanp/setup-racket@v1.15
with:
architecture: ${{ matrix.racket-arch }}
distribution: full
variant: CS
version: '9.3'
- name: Install pinned dependencies
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/keel.git#${KEEL_PIN}"
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#${RIVET_PIN}"
- name: Run executable acceptance contract
shell: bash
run: |
set -euo pipefail
mkdir -p .rivet/acceptance
raco rivet acceptance --json | tee .rivet/acceptance/report.json
- name: Upload macOS evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: taskly-acceptance-macos-${{ matrix.arch }}
path: .rivet/acceptance
if-no-files-found: warn

linux:
name: Linux packaged GTK4 acceptance
runs-on: ubuntu-24.04
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: Set up embeddable Racket CS
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@b1d05950537f8679b2cfb48aded383c5162c288f
- name: Install native and pinned Racket dependencies
shell: bash
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install --yes at-spi2-core dbus-x11 libgtk-4-dev \
libadwaita-1-dev libjson-glib-dev libsecret-1-dev python3-pyatspi weston
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/keel.git#${KEEL_PIN}"
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#${RIVET_PIN}"
raco pkg install --auto --no-docs --skip-installed db crypto
- name: Run executable acceptance contract
shell: bash
run: |
set -euo pipefail
mkdir -p .rivet/acceptance
dbus-run-session -- bash -c \
'set -euo pipefail; raco rivet acceptance --json | tee .rivet/acceptance/report.json'
- name: Upload Linux evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: taskly-acceptance-linux
path: .rivet/acceptance
if-no-files-found: warn
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ permissions:
# Single source of truth for the rivet pin; every install step below
# references this so a bump is one edit, not a grep-replace across jobs.
env:
RIVET_PIN: c7f9881d25acea50babd51a2a48672ce0cf99b1f
RIVET_PIN: b1d05950537f8679b2cfb48aded383c5162c288f

jobs:
contract:
Expand Down Expand Up @@ -73,7 +73,7 @@ jobs:
# Builds and caches the embeddable Racket CS runtime and exports
# RIVET_RACKET_* (rivet's own composite action).
- name: Set up embeddable Racket CS
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@v0.11.0
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@b1d05950537f8679b2cfb48aded383c5162c288f
# Installed after the embeddable runtime so the catalog dependency
# closure (cext-lib, crypto-lib, keel) resolves against the minimal
# build — it ships no packages of its own. db backs taskly's storage;
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ concurrency:
# Single source of truth for the rivet pin; every install step below
# references this so a bump is one edit, not a grep-replace across jobs.
env:
RIVET_PIN: c7f9881d25acea50babd51a2a48672ce0cf99b1f
RIVET_PIN: b1d05950537f8679b2cfb48aded383c5162c288f

jobs:
macos:
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up embeddable Racket CS
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@v0.11.0
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@b1d05950537f8679b2cfb48aded383c5162c288f
# Installed after the embeddable runtime so the catalog dependency
# closure (cext-lib, crypto-lib, keel) resolves against the minimal
# build — it ships no packages of its own. db backs taskly's storage;
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ Thumbs.db
## Testing
coverage/
*.log
__pycache__/
*.py[cod]

## Local workspace artifacts (not part of the project)
Noise/
Expand Down
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Taskly 是 Rivet 架构的待办应用(monorepo):**一个 Racket CS 领域
```bash
# 核心(任何平台)
raco rivet build # 生成客户端 + 编译核心 bundle + 构建当前平台宿主
raco rivet acceptance --json # 当前平台完整验收:契约、真实 RVT1、构建、打包、原生 UI
raco test racket/ # 契约测试
scripts/taskly-cli.sh list --json # dev CLI(系统 racket)
python3 shared/cli-golden/runner.py --binary scripts/taskly-cli.sh # 63 例 golden 套件(--record 重录)
Expand All @@ -33,6 +34,14 @@ scripts/sync-i18n.sh # shared/i18n → 各宿主资源目录
scripts/sync-i18n.sh --check # CI 模式:仅校验
```

开始修改已有 checkout 前,必须先检查 `git status`,再以 fast-forward-only
方式更新 tracked remote。工作树非干净、分支已分叉或远端不可达时,保留现场并
报告,不得用 stash/reset/清理生成目录掩盖状态。记录本次工作的起始 commit。

`rivet-acceptance.rktd` 是可执行的完成定义。Agent 不得通过删除 requirement、
放宽断言、改 golden 期望或用 CLI 代替 GUI 行为来让变更变绿;required suite
只要在声明平台未执行、超时、失败或没有生成本次运行的新证据,就不能称为完成。

发布 = 改 `VERSION` + `rivet.rktd` + `CHANGELOG.md`(三处同值)→ 推 tag `v*`,release.yml 自动打包三平台 + SHA256SUMS + 签名 update manifest。

## 契约文档(改任何行为前必读,改动必须同步契约)
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed
- Add an executable cross-platform product acceptance contract. Windows,
macOS arm64/x64, and Linux now run shared Racket contracts, a real RVT1
embedded-backend round trip, native compilation, verified packaging, and an
accessibility-driven critical UI flow against a disposable user profile.
The gate archives fresh interaction, accessibility, and platform diagnostic
evidence; fixed-sleep boot smoke and CLI substitutions no longer count as
proof of native product behavior.

## [0.2.3] - 2026-10-17

### Changed
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ macOS, CMake + GTK 4 headers on Linux, Windows App SDK on Windows):

```bash
raco rivet build # generate clients, compile the core, build the host
raco rivet acceptance --json # full current-platform product evidence
raco test racket/ # contract tests
python3 shared/cli-golden/runner.py --binary scripts/taskly-cli.sh # 63-case golden suite
```
Expand All @@ -126,8 +127,11 @@ python3 shared/cli-golden/runner.py --binary scripts/taskly-cli.sh # 63-case g
One spec keeps every surface consistent: product behavior
([PRODUCT-SPEC](shared/spec/PRODUCT-SPEC.md)), data format, CLI contract,
design tokens, and the update contract — all under
[shared/spec/](shared/spec/). CI enforces the CLI contract with the
golden suite and byte-identical i18n on every push.
[shared/spec/](shared/spec/). CI enforces the CLI contract with the golden
suite and byte-identical i18n on every push. The source-controlled
`rivet-acceptance.rktd` additionally requires real RVT1, native build,
verified package, and accessibility-driven product behavior on Windows,
macOS arm64/x64, and Linux.

## License

Expand Down
3 changes: 2 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ racket racket/taskly/cli.rkt install-cli # 安装到 ~/.local/bin/taskly

```bash
raco rivet build # 生成客户端、编译核心、构建宿主
raco rivet acceptance --json # 当前平台完整产品验收证据
raco test racket/ # 契约测试
python3 shared/cli-golden/runner.py --binary scripts/taskly-cli.sh # 63 例 golden 套件
```
Expand All @@ -80,7 +81,7 @@ python3 shared/cli-golden/runner.py --binary scripts/taskly-cli.sh # 63 例 go
└─────────────────────────────┘ └────────────────────────────┘
```

一套规格保证所有端一致:产品行为([PRODUCT-SPEC](shared/spec/PRODUCT-SPEC.md))、数据格式、CLI 契约、设计令牌、更新契约——都在 [shared/spec/](shared/spec/) 下。CI 在每次推送时用 golden 套件和字节级一致的 i18n 强制执行 CLI 契约。
一套规格保证所有端一致:产品行为([PRODUCT-SPEC](shared/spec/PRODUCT-SPEC.md))、数据格式、CLI 契约、设计令牌、更新契约——都在 [shared/spec/](shared/spec/) 下。CI 在每次推送时用 golden 套件和字节级一致的 i18n 强制执行 CLI 契约;源码中的 `rivet-acceptance.rktd` 还要求 Windows、macOS arm64/x64、Linux 分别通过真实 RVT1、原生构建、已验证安装包和无障碍树驱动的产品行为验收。

## 许可证

Expand Down
14 changes: 14 additions & 0 deletions linux/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,11 @@ void rebuild_task_list() {
(g_state.api == nullptr ? tr("taskListInputHintNoDb")
: tr("taskListInputHint"))
.c_str());
if (g_state.quick_plus != nullptr) {
gtk_accessible_update_property(
GTK_ACCESSIBLE(g_state.quick_plus), GTK_ACCESSIBLE_PROPERTY_LABEL,
tr("taskListInputHint").c_str(), -1);
}
g_state.open_editor_id = -1;
return;
}
Expand Down Expand Up @@ -2843,6 +2848,9 @@ void apply_language() {
tr("searchHint").c_str());
gtk_entry_set_placeholder_text(g_state.quick_add,
tr("taskListInputHint").c_str());
gtk_accessible_update_property(
GTK_ACCESSIBLE(g_state.quick_add), GTK_ACCESSIBLE_PROPERTY_LABEL,
tr("taskListInputHint").c_str(), -1);
gtk_button_set_label(
GTK_BUTTON(g_state.show_completed_toggle),
(g_state.show_completed ? tr("hideCompletedToggle")
Expand Down Expand Up @@ -3375,13 +3383,19 @@ void on_activate(GtkApplication* app, gpointer) {
gtk_widget_set_hexpand(quick_box, TRUE);
auto* quick_plus =
gtk_button_new_from_icon_name("list-add-symbolic");
gtk_accessible_update_property(
GTK_ACCESSIBLE(quick_plus), GTK_ACCESSIBLE_PROPERTY_LABEL,
tr("taskListInputHint").c_str(), -1);
gtk_widget_add_css_class(quick_plus, "quick-add-plus");
gtk_widget_set_valign(quick_plus, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start(quick_plus, 7);
gtk_widget_set_opacity(quick_plus, 0.45);
gtk_box_append(GTK_BOX(quick_box), quick_plus);
auto* quick_add = gtk_entry_new();
gtk_widget_add_css_class(quick_add, "quick-add");
gtk_accessible_update_property(
GTK_ACCESSIBLE(quick_add), GTK_ACCESSIBLE_PROPERTY_LABEL,
tr("taskListInputHint").c_str(), -1);
gtk_widget_set_hexpand(quick_add, TRUE);
gtk_box_append(GTK_BOX(quick_box), quick_add);
auto* preview = gtk_label_new("");
Expand Down
2 changes: 2 additions & 0 deletions macos-host/Sources/RivetHost/TaskPaneView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ struct TaskPaneView: View {
.opacity(model.quickAddText.trimmingCharacters(in: .whitespaces).isEmpty ? 0.45 : 1)
}
.buttonStyle(.plain)
.accessibilityIdentifier("quick-add-submit")
.help(model.t("taskListInputHint"))

TextField(model.t("taskListInputHint"), text: $model.quickAddText)
Expand All @@ -159,6 +160,7 @@ struct TaskPaneView: View {
.onChange(of: model.quickAddText) { _, _ in
model.quickAddTextChanged()
}
.accessibilityIdentifier("quick-add")

if let preview = model.quickAddPreview {
HStack(spacing: 4) {
Expand Down
1 change: 1 addition & 0 deletions macos-host/Sources/RivetHost/TaskRowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ struct TaskRowView: View {
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityIdentifier("task-complete-\(task.id)")
}

// MARK: - Due-date semantics (DESIGN-TOKENS)
Expand Down
2 changes: 1 addition & 1 deletion racket/taskly/backend.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
;; Test seam: the generated RPC handlers are plain functions; contract
;; tests import them directly without standing up an RVT1 server.
(module+ rpcs
(provide get_setting set_setting open_database))
(provide get_setting set_setting open_database close_database))

;; The managed development host speaks the exact same RVT1 protocol over
;; stdin/stdout. This is deliberately only a transport alternative: the
Expand Down
4 changes: 4 additions & 0 deletions racket/tests/backend-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,8 @@
(check-equal? (config-ref (read-config legacy-config) "last_db_path")
"/sync/old.db")))
(lambda ()
;; open_database installs a process-wide service just like the embedded
;; host. Close it before removing the disposable profile so Windows does
;; not retain an open SQLite handle during test cleanup.
(close_database)
(delete-directory/files temp-root)))
66 changes: 66 additions & 0 deletions rivet-acceptance.rktd
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#hasheq(
(version . 1)
(requirements
. (#hasheq((kind . contract) (platforms . (windows macos linux)))
#hasheq((kind . bridge) (platforms . (windows macos linux)))
#hasheq((kind . native-build) (platforms . (windows macos linux)))
#hasheq((kind . package) (platforms . (windows macos linux)))
#hasheq((kind . native-ui) (platforms . (windows macos linux)))))
(suites
. (#hasheq((id . core-contract)
(kind . contract)
(platforms . (windows macos linux))
(command . ("raco" "test"
"racket/tests/backend-test.rkt"
"racket/tests/cli-update-test.rkt"
"racket/tests/core-test.rkt"
"racket/tests/rivet-schema-test.rkt"
"racket/tests/updater-test.rkt"))
(timeout-seconds . 600))
#hasheq((id . embedded-wire)
(kind . bridge)
(platforms . (windows macos linux))
(command . ("raco" "test" "racket/tests/backend-wire-test.rkt"))
(timeout-seconds . 300))
#hasheq((id . native-host)
(kind . native-build)
(platforms . (windows macos linux))
(command . ("raco" "rivet" "build"))
(timeout-seconds . 1800))
#hasheq((id . windows-package)
(kind . package)
(platforms . (windows))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/windows/package.json")))
#hasheq((id . macos-package)
(kind . package)
(platforms . (macos))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/macos/package.json")))
#hasheq((id . linux-package)
(kind . package)
(platforms . (linux))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/linux/package.json")))
#hasheq((id . windows-native-ui)
(kind . native-ui)
(platforms . (windows))
(command . ("pwsh" "-NoProfile" "-File"
"scripts/e2e/e2e-windows.ps1"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/windows/interaction-trace.json")))
#hasheq((id . macos-native-ui)
(kind . native-ui)
(platforms . (macos))
(command . ("bash" "scripts/e2e/e2e-macos.sh"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/macos/interaction-trace.json")))
#hasheq((id . linux-native-ui)
(kind . native-ui)
(platforms . (linux))
(command . ("bash" "scripts/e2e/e2e-linux.sh"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/linux/interaction-trace.json"))))))
Loading
Loading