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
38 changes: 38 additions & 0 deletions .agents/skills/papyrus-client/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: papyrus-client
description: Implement or debug Papyrus Flutter UI, reader integration, local storage, and platform behavior in client/app.
---

Read `client/AGENTS.md` from the Papyrus workspace. Work in `client/app`; use the
workspace's `tools/flutter`, `tools/dart`, and `tools/papyrus` for the pinned SDK.
Locate the workspace by ascending from the active directory to `.fvmrc` and `tools/papyrus`.

Trace a feature from its page/widget through the Provider state to its repository
or service. `lib/main.dart` owns composition; `lib/config/app_router.dart` owns
navigation. Inspect existing tests in the corresponding `test/` domain before
choosing a regression. For purely visual edits, inspect shared theme tokens and
responsive widgets rather than adding a second style system.

For persisted changes, follow writes and subscriptions in `lib/data` and
`lib/powersync`. A successful UI update is not evidence that an offline edit was
saved. Account for guest/user/server profile switches and scoped media caches.
For e-ink UI, inspect `lib/themes/app_motion.dart` and e-ink tests. For platform
changes, preserve conditional imports and validate the relevant target.

For reading changes, inspect `lib/reader` and the actual `papyrus_reader` revision
in `pubspec.yaml`/`pubspec.lock`. The sibling reader checkout is independent. EPUB
and PDF locators have different semantics; preserve restart/resume and progress.

Use Dart MCP with `client/app` as a root when available for diagnostics, tests and
running-app inspection. CLI checks remain available when MCP is not attached.
Consult Context7 or official docs for framework APIs specific to the installed SDK.

Run focused tests and `tools/papyrus check client`; for platform adapter changes,
also compile web or the affected available native target. Run the full suite when
the change spans shared composition/persistence. Distinguish analyzer warnings,
formatting debt, actual failures, and unrun targets. The OPDS network smoke test
needs `OPDS_SMOKE_URL`; it is not part of an ordinary offline test run.

If wire payloads, auth flows, or sync fields change, also read the workspace
`papyrus-sync-contract` skill. Do not broaden a client-only task into a backend
refactor unless the behavior requires it.
4 changes: 4 additions & 0 deletions .agents/skills/papyrus-client/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Papyrus Client"
short_description: "Flutter UI, persistence, and platform checks"
default_prompt: "Use $papyrus-client to implement and verify this Papyrus client change."
39 changes: 39 additions & 0 deletions .agents/skills/papyrus-server/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
name: papyrus-server
description: Implement or debug Papyrus FastAPI services, PostgreSQL persistence, auth, media, and Alembic migrations in server.
---

Read `server/AGENTS.md` from the Papyrus workspace and work from `server/`. Locate
the workspace by ascending to `.fvmrc` and `tools/papyrus`. Use locked `uv` tools;
`tools/papyrus deps server` installs existing dev dependencies without upgrading.

Trace an endpoint from `papyrus/api/routes` through `papyrus/services` into schemas
and SQLAlchemy models. Keep domain rules and transaction orchestration in services.
New router modules need registration in `papyrus/api/routes/__init__.py`; new models
need exports through `papyrus.models` for Alembic discovery.

For ownership-sensitive changes, trace the authenticated user through every query,
referenced entity and media path. For sync changes, preserve atomic batches,
owner-level serialization, deletion tombstones and physical deletion after commit.
Inspect `papyrus/services/library_sync.py` and `sync.py` for the existing semantics.

For persisted schema changes, update models and add/review an Alembic revision.
Inspect both the existing database migration tests and affected domain tests.
Autogeneration needs a running database at the expected current revision; a generated
file alone does not verify an upgrade or data preservation. Apply migrations to a
disposable database when validation needs it. Honor existing approval requirements
for destructive production migrations.

Use `tools/papyrus test server -- tests/<focused_path>.py`. The test fixtures create
roles/databases and drop/recreate tables. They need local PostgreSQL even without an
`integration` marker. Do not run overlapping suites on the same test database.
CLI runs exclude externally backed `auth_smoke` tests; provider smoke testing is a
separate opt-in workflow. Do not print environment files or rotated auth tokens.

Run `tools/papyrus check server` for Ruff lint, non-mutating formatting and the
repo's configured strict Mypy. Mypy is installed and used by server CI; adding a
different type checker is not required. If sandbox TS changes, install with
`npm --prefix frontend/dev-pages ci`, then run its `typecheck` and `build` scripts.

For auth, upload or PowerSync wire changes, also read the workspace
`papyrus-sync-contract` skill and inspect the corresponding client tests.
4 changes: 4 additions & 0 deletions .agents/skills/papyrus-server/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Papyrus Server"
short_description: "FastAPI services, migrations, and database tests"
default_prompt: "Use $papyrus-server to implement and verify this Papyrus backend change."
38 changes: 38 additions & 0 deletions .agents/skills/papyrus-sync-contract/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: papyrus-sync-contract
description: Coordinate or review Papyrus client/server changes to auth, PowerSync schemas, offline uploads, ownership, and media contracts.
---

Use for changes that cross the client/server boundary. Find the Papyrus workspace
by ascending to `.fvmrc` and `tools/papyrus`; read the affected component AGENTS.md.
Read [contract map](references/contract-map.md) for exact code and test locations.
Check only the contract involved in the request.

For a synced field, follow the full round trip: Dart model and mapper -> SQLite
column -> queued CRUD serialization -> server Pydantic validation -> owned SQLAlchemy
row -> PowerSync SELECT/alias -> client decoding and repository subscription.
Check ID aliases, omitted versus null fields, booleans, UTC timestamps, JSON text,
foreign-key ownership and local-only guest behavior. Update all required layers
together; a route test alone cannot prove the replicated field round trip.

Preserve upload transaction acknowledgment after successful server persistence.
Failed batches must remain retryable. Server batches serialize per owner and commit
atomically; deleting physical media occurs after commit. Tombstones stop delayed
offline writes from reviving deleted entities. Tests should prove the affected
failure mode, including account/server profile isolation when relevant.

For auth, trace refresh, token expiry, logout and profile transition as well as the
successful sign-in. Distinguish opaque refresh tokens, short-lived API access JWTs
and PowerSync credentials. Keep browser callbacks/deep links and API prefixes
compatible with the client's configured server URI.

Choose focused client and server regressions from the reference map. Run the relevant
component checks. A live sync test needs the API, PowerSync, publication/replication,
keys and a supported native test target; skipped live tests must be reported as
unrun. Do not modify user libraries or purge local state to make a test pass.

For coordinated implementation, settle payload/schema decisions before assigning
disjoint client/server edits. If delegation is requested, use client/server roles
and the contract reviewer. Serialize database tests on each shared test database.
Report the contract change, tests on both sides, migration needs and unverified
round-trip behavior.
4 changes: 4 additions & 0 deletions .agents/skills/papyrus-sync-contract/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface:
display_name: "Papyrus Sync Contract"
short_description: "Auth and offline sync across client and server"
default_prompt: "Use $papyrus-sync-contract to trace and verify this Papyrus contract change."
52 changes: 52 additions & 0 deletions .agents/skills/papyrus-sync-contract/references/contract-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Contract entry points

Paths below are relative to the Papyrus workspace. Verify the current source before
changing a contract; this map identifies owners rather than freezing field lists.

| Contract | Client | Server |
| --- | --- | --- |
| API base/prefix | `client/app/lib/auth/papyrus_api_config.dart` | `server/papyrus/config.py`, `server/papyrus/main.py` |
| Auth/token lifecycle | `client/app/lib/auth/{auth_api_client,auth_repository,token_store}.dart`, `lib/providers/auth_provider.dart` | `server/papyrus/api/routes/auth.py`, `schemas/auth.py`, `services/auth/` |
| PowerSync credentials and upload queue | `client/app/lib/powersync/papyrus_powersync_connector.dart` | `server/papyrus/api/routes/sync.py`, `schemas/sync.py`, `services/sync.py` |
| Library schema and row mapping | `client/app/lib/powersync/{papyrus_schema,powersync_book_mapper,library_row_mapper,library_database}.dart` | `server/papyrus/models/sync.py`, `schemas/book.py`, `services/library_validation.py`, `services/library_sync.py` |
| Replication projection | `client/app/lib/powersync/papyrus_schema.dart` | `server/powersync/sync-config.yaml`, `scripts/setup_local_powersync.sh` |
| Profiles and repository watches | `client/app/lib/powersync/{powersync_service,sync_profile_switch_queue}.dart`, `lib/data/data_store.dart` | JWT user identity and row ownership predicates |
| Media upload/cache | `client/app/lib/media/`, `lib/services/book_download_service*` | `server/papyrus/api/routes/media.py`, `services/media.py`, `schemas/media.py` |
| OPDS relay | `client/app/lib/opds/opds_http_client.dart` | `server/papyrus/api/routes/opds.py`, `services/opds.py`, `schemas/opds.py`, `docs/opds-relay.md` |
| Managed acquisition | `client/app/lib/acquisition/`, `lib/providers/acquisition_downloads_provider.dart` | `server/papyrus/api/routes/acquisition.py`, `services/acquisition.py`, `services/acquisition_monitor.py` |

## Useful existing regressions

- Client sync: `client/app/test/powersync/`, especially mapper, persistence, connector,
live-library, schema-mode and profile-switch tests.
- Client auth: `client/app/test/auth/`, `test/providers/auth_provider_test.dart`.
- Client media isolation: `client/app/test/media/media_profile_switch_contract_test.dart`,
`media_storage_scope_test.dart`, and upload queue tests.
- Server batches/ownership/deletion: `server/tests/api/routes/test_sync.py`,
`test_library_sync.py`, `test_bookmark_sync.py`, `tests/services/test_sync.py`.
- Server schema/projection: `server/tests/test_powersync_sync_config.py`,
`test_library_migration.py`, `test_bookmark_migration.py`.
- Server auth: `server/tests/api/routes/test_auth.py`, `tests/services/test_auth.py`.
- End-to-end: `client/app/integration_test/powersync_books_integration_test.dart`
is gated by `RUN_POWERSYNC_INTEGRATION=true`. It registers temporary users and
validates two clients, offline reconnect, deletion and isolation against live
services. It imports `dart:io`; do not describe it as a browser integration test.

Example focused checks, from the workspace:

```bash
tools/papyrus test client -- test/powersync/papyrus_powersync_connector_test.dart
tools/papyrus test server -- tests/api/routes/test_sync.py tests/services/test_sync.py
```

For a deliberately requested live run on an available native device:

```bash
tools/papyrus test client -- integration_test/powersync_books_integration_test.dart \
-d macos --dart-define=RUN_POWERSYNC_INTEGRATION=true \
--dart-define-from-file=.dart_defines
```

Check `tools/flutter devices` first. Configure server auth/email requirements for
the disposable test users before running. Never equate the test's default skip
with a successful live sync run.
11 changes: 11 additions & 0 deletions .codex/agents/papyrus_client.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name = "papyrus_client"
description = "Flutter client implementation and focused widget, persistence, and platform tests."
developer_instructions = """
Work in the assigned client files. Read client/AGENTS.md and use the papyrus-client
skill when relevant. Trace UI state through providers into repositories. Preserve
offline writes, profile isolation, theme tokens, responsive layouts and e-ink motion.
Use the pinned SDK and run focused analysis/tests. Editing reader/ does not change
the Git-pinned client dependency. Share HTTP or sync contract changes with the parent.
Report changed files, verification, and remaining uncertainty. Do not change another
agent's files or update submodule revisions without the parent assigning that work.
"""
13 changes: 13 additions & 0 deletions .codex/agents/papyrus_contract_reviewer.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name = "papyrus_contract_reviewer"
description = "Read-only review of client/server auth, offline sync, ownership, and reader compatibility."
sandbox_mode = "read-only"
developer_instructions = """
Review the assigned diff and trace affected callers across the client/server boundary.
Use the papyrus-sync-contract skill. Check wire names, IDs, null clearing, serialized
JSON, booleans/timestamps, profile isolation, auth refresh, queue acknowledgement,
ownership, atomicity and tombstones where relevant. For reader changes check the
client's actual pinned dependency. Prioritize concrete regressions with file/line
evidence and a reproduction or missing regression test. Do not edit files, install
packages, start services or run database tests. Separate confirmed bugs from unknowns.
Return concise actionable findings and any material verification gaps.
"""
11 changes: 11 additions & 0 deletions .codex/agents/papyrus_server.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name = "papyrus_server"
description = "FastAPI services, schemas, async persistence, migrations, and focused backend tests."
developer_instructions = """
Work in the assigned server files. Read server/AGENTS.md and use the papyrus-server
skill when relevant. Keep routes thin and business rules in async services. Preserve
ownership checks, atomic sync batches, tombstones and post-commit media cleanup.
Include reviewed migrations when persisted schemas change. Use locked uv tools and
coordinate database tests with the parent; fixtures recreate tables and cannot share
a database concurrently. Run relevant tests, Ruff and Mypy. Share contract changes
with the parent. Report changed files, verification, and remaining uncertainty.
"""
8 changes: 8 additions & 0 deletions .codex/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[agents]
max_concurrent_threads_per_session = 2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace the invalid agent concurrency setting

When Codex loads this trusted-project configuration, it fails before starting a session or MCP server: validating agents.max_concurrent_threads_per_session=2 with codex exec --strict-config reports invalid type: integer '2', expected struct AgentRoleToml in agents, whereas agents.max_threads=2 is accepted. Rename this setting to max_threads; otherwise Codex is unusable in the repository rather than merely failing to enforce the intended two-agent limit.

Useful? React with 👍 / 👎.

Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register the custom agent role files

Even after correcting the concurrency key, Codex custom roles must be declared as [agents.<role>] entries with description and config_file; placing TOMLs in .codex/agents alone does not register them. A repo-wide search finds no agents.papyrus_* tables or config_file references, so the advertised client, server, and contract-review roles cannot be selected when delegation is requested. Register each added role under this section.

AGENTS.md reference: AGENTS.md:L56-L58

Useful? React with 👍 / 👎.


[mcp_servers.dart]
command = "bash"
args = ["-c", 'while [ ! -x tools/dart-mcp ]; do if [ "$PWD" = / ]; then echo "Papyrus workspace not found" >&2; exit 1; fi; cd ..; done; exec ./tools/dart-mcp']
startup_timeout_sec = 120
tool_timeout_sec = 120
6 changes: 6 additions & 0 deletions .fvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"flutter": "3.41.2",
"runPubGetOnSdkChanges": false,
"updateVscodeSettings": false,
"updateGitIgnore": false
}
34 changes: 34 additions & 0 deletions .github/workflows/tooling.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Workspace tooling

on:
push:
paths:
- 'tools/**'
- '.vscode/setup.sh'
- '.github/workflows/tooling.yml'
pull_request:
paths:
- 'tools/**'
- '.vscode/setup.sh'
- '.github/workflows/tooling.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
tooling:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
- name: Check workspace commands
run: tools/papyrus check tooling
- name: Check setup script
run: shellcheck .vscode/setup.sh
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.DS_Store
.fvm/
.local/
__pycache__/
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"Dart-Code.flutter",
"Dart-Code.dart-code",
"ms-python.python",
"charliermarsh.ruff",
"openai.chatgpt"
]
}
24 changes: 24 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"dart.flutterSdkPath": ".fvm/flutter_sdk",
"dart.projectSearchDepth": 6,
"python.defaultInterpreterPath": "${workspaceFolder}/server/.venv/bin/python",
"python.analysis.extraPaths": ["${workspaceFolder}/server"],
"ruff.path": ["${workspaceFolder}/server/.venv/bin/ruff"],
"git.detectSubmodules": true,
"git.autoRepositoryDetection": "subFolders",
"[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": true
},
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true
},
"files.watcherExclude": {
"**/.fvm/**": true,
"**/.local/**": true,
"**/.venv/**": true,
"**/.dart_tool/**": true,
"**/build/**": true
}
}
9 changes: 6 additions & 3 deletions .vscode/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ workspace_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
client_dir="${workspace_root}/client/app"
server_dir="${workspace_root}/server"

required_commands=(git flutter docker uv openssl)
required_commands=(git docker uv openssl)

for command_name in "${required_commands[@]}"; do
if ! command -v "${command_name}" >/dev/null 2>&1; then
Expand All @@ -24,6 +24,9 @@ echo "Initializing workspace projects..."
git -C "${workspace_root}" submodule sync --recursive
git -C "${workspace_root}" submodule update --init --recursive

echo "Preparing project Flutter SDK..."
"${workspace_root}/tools/papyrus" sdk

echo "Preparing client..."
if [[ ! -f "${client_dir}/.dart_defines" ]]; then
cp "${client_dir}/.dart_defines.example" "${client_dir}/.dart_defines"
Expand All @@ -32,7 +35,7 @@ fi

(
cd "${client_dir}"
flutter pub get
"${workspace_root}/tools/flutter" pub get --enforce-lockfile
)

echo "Preparing server..."
Expand All @@ -43,7 +46,7 @@ fi

(
cd "${server_dir}"
uv sync --extra dev
uv sync --locked --extra dev

set -a
# shellcheck disable=SC1091
Expand Down
Loading
Loading