@maudecode/kanvil 0.0.0-stage → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -2
- package/apps/server/.env.example +41 -0
- package/apps/server/dist/auth-BscXb1NQ.mjs +2666 -0
- package/apps/server/dist/config-Qxr_7oqZ.mjs +575 -0
- package/apps/server/dist/configure.mjs +23 -0
- package/apps/server/dist/index.mjs +1754 -0
- package/apps/server/dist/migrate-C9f4ozB4.mjs +17 -0
- package/apps/server/dist/migrate.mjs +12 -0
- package/apps/server/dist/recover.mjs +52 -0
- package/apps/server/drizzle/0000_foundation.sql +437 -0
- package/apps/server/drizzle/0001_claim_identity.sql +1 -0
- package/apps/server/drizzle/0002_transactional_attachments.sql +6 -0
- package/apps/server/drizzle/0003_durable_claim_fence.sql +4 -0
- package/apps/server/drizzle/0004_lumpy_archangel.sql +6 -0
- package/apps/server/drizzle/0005_unique_toxin.sql +28 -0
- package/apps/server/drizzle/0006_instance_services.sql +48 -0
- package/apps/server/drizzle/0007_optional_broker.sql +6 -0
- package/apps/server/drizzle/0008_workspaces.sql +37 -0
- package/apps/server/drizzle/0009_project_icons.sql +6 -0
- package/apps/server/drizzle/0010_project_labels.sql +14 -0
- package/apps/server/drizzle/0011_due_dates.sql +1 -0
- package/apps/server/drizzle/0012_label_colors.sql +7 -0
- package/apps/server/drizzle/meta/0000_snapshot.json +3210 -0
- package/apps/server/drizzle/meta/0001_snapshot.json +3216 -0
- package/apps/server/drizzle/meta/0002_snapshot.json +3255 -0
- package/apps/server/drizzle/meta/0003_snapshot.json +3262 -0
- package/apps/server/drizzle/meta/0004_snapshot.json +3302 -0
- package/apps/server/drizzle/meta/0005_snapshot.json +3518 -0
- package/apps/server/drizzle/meta/0006_snapshot.json +3842 -0
- package/apps/server/drizzle/meta/0007_snapshot.json +3874 -0
- package/apps/server/drizzle/meta/0008_snapshot.json +4051 -0
- package/apps/server/drizzle/meta/0009_snapshot.json +4105 -0
- package/apps/server/drizzle/meta/0010_snapshot.json +4174 -0
- package/apps/server/drizzle/meta/0011_snapshot.json +4180 -0
- package/apps/server/drizzle/meta/0012_snapshot.json +4226 -0
- package/apps/server/drizzle/meta/_journal.json +97 -0
- package/apps/web/dist/apple-touch-icon.png +0 -0
- package/apps/web/dist/assets/CompositeItem-D9KvUNiq.js +1 -0
- package/apps/web/dist/assets/admin-settings-B0u2-aAK.js +1 -0
- package/apps/web/dist/assets/camera-Cv5wKkKn.js +1 -0
- package/apps/web/dist/assets/date-picker-lhXxKIlN.js +1 -0
- package/apps/web/dist/assets/ellipsis-CN74O_EP.js +1 -0
- package/apps/web/dist/assets/forms-456UbfIC.js +92 -0
- package/apps/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/apps/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/apps/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/apps/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/apps/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/apps/web/dist/assets/index-CVzq9BMC.css +2 -0
- package/apps/web/dist/assets/index-Dt8wYIVA.js +51 -0
- package/apps/web/dist/assets/instance-BCNvOvYa.js +1 -0
- package/apps/web/dist/assets/list-order-1UoOT5nR.js +1 -0
- package/apps/web/dist/assets/lucide-U995NW8E.js +1 -0
- package/apps/web/dist/assets/member-directory-C81ugis6.js +1 -0
- package/apps/web/dist/assets/popover-BWk65ByY.js +1 -0
- package/apps/web/dist/assets/project-settings-DnpVfVf5.js +1 -0
- package/apps/web/dist/assets/rotate-ccw-BLeWpuu1.js +1 -0
- package/apps/web/dist/assets/settings-DUXuOgWn.js +1 -0
- package/apps/web/dist/assets/settings-layout-xR4hrfte.js +1 -0
- package/apps/web/dist/assets/table-BQbZG4BK.js +1 -0
- package/apps/web/dist/assets/ticket-detail-B9M_eaVu.js +2 -0
- package/apps/web/dist/assets/ticket-editor-Chg1lDuU.js +1 -0
- package/apps/web/dist/assets/ticket-list-C8500tXj.js +5 -0
- package/apps/web/dist/assets/ticket-tags-cbwQ93Gt.js +1 -0
- package/apps/web/dist/assets/upload-5Wg5ou_Z.js +1 -0
- package/apps/web/dist/assets/workspace-settings-Dte44Kkx.js +1 -0
- package/apps/web/dist/brand/icon-192.png +0 -0
- package/apps/web/dist/brand/icon-512.png +0 -0
- package/apps/web/dist/brand/mark-blue.svg +1 -0
- package/apps/web/dist/brand/mark-white.svg +1 -0
- package/apps/web/dist/brand/mark.svg +1 -0
- package/apps/web/dist/brand/maskable-512.png +0 -0
- package/apps/web/dist/brand/social.png +0 -0
- package/apps/web/dist/favicon.ico +0 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/fonts/OFL.txt +93 -0
- package/apps/web/dist/fonts/ibm-plex-sans-latin.woff2 +0 -0
- package/apps/web/dist/icon.svg +1 -0
- package/apps/web/dist/index.html +31 -0
- package/apps/web/dist/site.webmanifest +29 -0
- package/apps/web/dist/third-party-icons.txt +64 -0
- package/apps/web/dist/third-party-themes.txt +548 -0
- package/bin/kanvil.mjs +27 -0
- package/compose.yml +66 -0
- package/docs/api.md +87 -0
- package/docs/branding.md +13 -0
- package/docs/design.md +37 -0
- package/docs/infrastructure.md +94 -0
- package/docs/mobile.md +77 -0
- package/docs/plan.md +111 -0
- package/docs/releasing.md +18 -0
- package/docs/self-hosting.md +204 -0
- package/docs/themes.md +38 -0
- package/docs/verification.md +83 -0
- package/docs/workspaces.md +21 -0
- package/package.json +45 -4
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Self-hosting
|
|
2
|
+
|
|
3
|
+
Kanvil runs as one application service and PostgreSQL 18. Tickets, comments, authentication, durable events, and attachment bytes live in PostgreSQL. PocketID is optional; local login remains available alongside OIDC.
|
|
4
|
+
|
|
5
|
+
## Install from npm
|
|
6
|
+
|
|
7
|
+
Requires Node `>=24.22.0 <25` and a PostgreSQL 18 server. The package includes the backend, built web app, and schema migrations; pnpm and a source checkout are unnecessary.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install --global @maudecode/kanvil
|
|
11
|
+
mkdir kanvil-instance
|
|
12
|
+
cd kanvil-instance
|
|
13
|
+
kanvil configure --production https://kanvil.example.com
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`configure` creates a mode-600 `.env` in the current directory and refuses to overwrite it. Edit `DATABASE_URL` to point at your PostgreSQL database, using a role that owns the database and can run schema migrations. For a PostgreSQL service on the same host, an administrator can create it with `createuser --pwprompt kanvil` and `createdb --owner=kanvil kanvil`. Set `DATABASE_URL=postgres://kanvil:URL_ENCODED_PASSWORD@127.0.0.1:5432/kanvil` and `HOST=127.0.0.1` in `.env`. The generated `POSTGRES_PASSWORD` belongs to the Compose path; use the password you assigned to the standalone database role instead.
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
kanvil start
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run from the instance directory so the CLI reads its `.env`. Startup defaults to production mode and applies migrations before listening. `kanvil migrate` applies migrations without starting HTTP; `kanvil recover --email admin@example.com` prompts privately for a replacement local password. `kanvil --version` reports the installed package version. Use a service manager to keep the process running, with the instance directory as its working directory and the absolute path from `command -v kanvil` as its executable. Run it as an unprivileged account that can read the configuration.
|
|
23
|
+
|
|
24
|
+
For an upgrade, back up PostgreSQL and `.env`, stop the service, install the desired version with `npm install --global @maudecode/kanvil@VERSION`, then restart from the same instance directory. `@latest` selects the latest stable release. Keep the same database URL and authentication secret. See the standalone backup instructions below before upgrading.
|
|
25
|
+
|
|
26
|
+
## Install the published Docker image
|
|
27
|
+
|
|
28
|
+
Install Docker with Compose. No Node installation or source checkout is required. In an empty instance directory, download `compose.yml` from the [release's tagged source](https://github.com/MaudeCode/kanvil/tags). Replace `X.Y.Z` with a published version:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
kanvil_version=X.Y.Z
|
|
32
|
+
curl -fL "https://raw.githubusercontent.com/MaudeCode/kanvil/v${kanvil_version}/compose.yml" -o compose.yml
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Create `.env` with an HTTPS origin and independent secrets:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
umask 077
|
|
39
|
+
cat > .env <<EOF
|
|
40
|
+
KANVIL_URL=https://kanvil.example.com
|
|
41
|
+
POSTGRES_PASSWORD=$(openssl rand -hex 32)
|
|
42
|
+
BETTER_AUTH_SECRET=$(openssl rand -hex 48)
|
|
43
|
+
KANVIL_SETUP_TOKEN=$(openssl rand -hex 32)
|
|
44
|
+
KANVIL_IMAGE=ghcr.io/maudecode/kanvil:latest
|
|
45
|
+
EOF
|
|
46
|
+
docker compose pull
|
|
47
|
+
docker compose up -d --wait
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The image supports Linux amd64 and arm64. Replace `latest` with a release version such as `0.1.0`, or pin `ghcr.io/maudecode/kanvil@sha256:YOUR_DIGEST` for an immutable deployment.
|
|
51
|
+
|
|
52
|
+
The app binds to loopback port 5180; change the host port with `KANVIL_PORT`. Set up the HTTPS reverse proxy below before opening the site. The application runs as the `node` user with a read-only root filesystem and temporary `/tmp`. Its health check verifies database connectivity. PostgreSQL storage is kept in the `postgres-data` volume. `docker compose down` retains that volume; removing volumes deletes instance data.
|
|
53
|
+
|
|
54
|
+
For source builds, use the repository's Node/pnpm versions and run:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
pnpm install --frozen-lockfile
|
|
58
|
+
pnpm configure --production https://kanvil.example.com
|
|
59
|
+
docker compose -f compose.yml -f compose.build.yml up -d --build --wait
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Keep both `-f` arguments on subsequent Compose operations for a source-built instance.
|
|
63
|
+
|
|
64
|
+
## First owner and configuration
|
|
65
|
+
|
|
66
|
+
Open the site, enter `KANVIL_SETUP_TOKEN` from your private configuration, and create the owner account. Setup closes after the first account exists. Create additional accounts in Administration → Users, then grant workspace access from the account detail sheet or workspace settings. Projects can inherit workspace roles or keep explicit private membership. Existing projects stay private when upgrading; see [workspaces](workspaces.md). Remove the setup token from configuration after onboarding if desired.
|
|
67
|
+
|
|
68
|
+
Retain `.env` securely. `BETTER_AUTH_SECRET` is required to read encrypted authentication state after restoration. It also encrypts saved integration credentials. Changing it is a deliberate credential rotation, not a routine upgrade. Both install paths serve the web app, API, MCP, and realtime at the same origin.
|
|
69
|
+
|
|
70
|
+
## HTTPS and streaming
|
|
71
|
+
|
|
72
|
+
Serve Kanvil at its own origin. A Caddy example is:
|
|
73
|
+
|
|
74
|
+
```caddyfile
|
|
75
|
+
kanvil.example.com {
|
|
76
|
+
reverse_proxy 127.0.0.1:5180
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
With nginx, forward the original host and protocol, turn off response buffering for SSE, and allow persistent streaming connections:
|
|
81
|
+
|
|
82
|
+
```nginx
|
|
83
|
+
location / {
|
|
84
|
+
proxy_pass http://127.0.0.1:5180;
|
|
85
|
+
proxy_set_header Host $host;
|
|
86
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
87
|
+
proxy_buffering off;
|
|
88
|
+
proxy_read_timeout 1h;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Keep `KANVIL_URL` equal to the public HTTPS origin. Add `KANVIL_TRUSTED_ORIGINS` only for additional browser clients you explicitly operate. Web, API, MCP, login, consent, and realtime use the same application origin. No separate message broker or search service is required.
|
|
93
|
+
|
|
94
|
+
Signed-in sessions and MCP OAuth clients may make `KANVIL_SESSION_WRITE_LIMIT` write requests per minute (120 by default) before receiving `429 rate_limited`; API keys are limited separately by the authentication layer. The counter is per application process.
|
|
95
|
+
|
|
96
|
+
## PocketID / OIDC
|
|
97
|
+
|
|
98
|
+
Create a confidential OIDC client in PocketID, enable PKCE, and register:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
https://kanvil.example.com/api/auth/callback/oidc
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Set all three values together in `.env`, then recreate the app:
|
|
105
|
+
|
|
106
|
+
```dotenv
|
|
107
|
+
OIDC_DISCOVERY_URL=https://identity.example.com/.well-known/openid-configuration
|
|
108
|
+
OIDC_CLIENT_ID=your-client-id
|
|
109
|
+
OIDC_CLIENT_SECRET=your-client-secret
|
|
110
|
+
OIDC_NAME=PocketID
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Sign in locally and explicitly link PocketID from account settings. Public signup and implicit account linking are closed. Use HTTPS for the provider, including native sign-in; the Expo integration requires it. The provider's ID-token signature is verified. For a private certificate authority, add a `compose.override.yml` pointing at its public certificate:
|
|
114
|
+
|
|
115
|
+
```yaml
|
|
116
|
+
services:
|
|
117
|
+
app:
|
|
118
|
+
environment:
|
|
119
|
+
NODE_EXTRA_CA_CERTS: /certificates/identity-ca.pem
|
|
120
|
+
volumes:
|
|
121
|
+
- ./identity-ca.pem:/certificates/identity-ca.pem:ro
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Native devices must also trust that authority. Never mount the authority's private key.
|
|
125
|
+
|
|
126
|
+
## Instance administration
|
|
127
|
+
|
|
128
|
+
Administrators can edit the instance name and description, manage accounts, and configure local/S3 file storage, PostgreSQL/Elasticsearch search, and PostgreSQL/RabbitMQ event delivery in Administration. See [optional infrastructure](infrastructure.md) for deployment variables, adapter interfaces, recovery behavior, and S3 backup requirements. OIDC, public URL, and push credentials remain deployment settings.
|
|
129
|
+
|
|
130
|
+
## Optional mobile push
|
|
131
|
+
|
|
132
|
+
Set `KANVIL_PUSH_ENABLED=true` to enable the Expo push transport. If the Expo project has push access-token security enabled, also set its server-held `EXPO_ACCESS_TOKEN`. Recreate the app after changing configuration. A signed mobile build must contain the public Expo project ID and have APNs/FCM credentials configured; see [mobile setup](mobile.md). Accounts opt in through Settings and register each device separately.
|
|
133
|
+
|
|
134
|
+
Push is disabled by default. Ticket storage, login, Inbox, realtime, and MCP work without Expo services. Push messages use a generic update notice and ticket identifiers, without ticket titles, descriptions, comments, or evidence. The database outbox commits with each subscribed event, checks current account/project/session/subscription access before every send, retries failures, and checks Expo receipts. Expired login sessions and explicit session revocation remove their device registrations. A failed or interrupted transport may deliver duplicates; queued notices expire after one day. Delivery status is available in the `push_deliveries` table. Backups contain device tokens, so protect them alongside authentication state.
|
|
135
|
+
|
|
136
|
+
## Backup
|
|
137
|
+
|
|
138
|
+
For a published Docker installation, run from the instance directory:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
umask 077
|
|
142
|
+
docker compose exec -T db pg_dump -U kanvil -d kanvil --format=custom > /secure/location/kanvil.dump
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use a new output filename and check that `pg_dump` succeeds before treating it as a backup. Replace `kanvil` after `-d` if `KANVIL_DATABASE` differs. For npm installations, use PostgreSQL 18 client tools against the configured database:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
umask 077
|
|
149
|
+
pg_dump --host=127.0.0.1 --username=kanvil --dbname=kanvil --format=custom --file=/secure/location/kanvil.dump
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Supply authentication through a protected `.pgpass` or a password prompt. Keep `.env` and any external S3 backups alongside the database backup. For a source checkout, the existing wrapper refuses to overwrite backup files:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
pnpm backup --output /secure/location/kanvil.dump
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The command uses the PostgreSQL tools inside the database container to create a consistent custom-format archive. It includes locally stored attachment/photo bytes, pending external uploads, authentication tables, and encrypted integration settings. Completed S3 uploads need a separate backup of each referenced bucket. The output is private and existing files are never overwritten. Keep the original instance configuration, especially `BETTER_AUTH_SECRET`, securely with your backup. Copy backups off the application host using your normal protected backup storage.
|
|
159
|
+
|
|
160
|
+
## Restore without replacing the original database
|
|
161
|
+
|
|
162
|
+
Stop the application before switching databases. For a published Docker install:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
docker compose stop app
|
|
166
|
+
docker compose exec -T db createdb -U kanvil kanvil_restored
|
|
167
|
+
docker compose exec -T db pg_restore -U kanvil -d kanvil_restored --exit-on-error --single-transaction < /secure/location/kanvil.dump
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For npm, stop the service, create an unused database with `createdb --host=127.0.0.1 --username=kanvil kanvil_restored`, then run `pg_restore --host=127.0.0.1 --username=kanvil --dbname=kanvil_restored --exit-on-error --single-transaction /secure/location/kanvil.dump`. Change `DATABASE_URL` in the instance `.env` to the restored database, retain the original authentication secret, and restart the service. A failed restore leaves the new target available for inspection; keep the original database.
|
|
171
|
+
|
|
172
|
+
For the source wrapper, restore into a new, unused database name:
|
|
173
|
+
|
|
174
|
+
```sh
|
|
175
|
+
pnpm restore --file /secure/location/kanvil.dump --database kanvil_restored
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The command refuses existing databases and restores in a single transaction. If it fails, the newly created target remains available for inspection; the previous database is retained. After a successful restore, set `KANVIL_DATABASE=kanvil_restored` in `.env`, retain the original authentication secret, and switch the application:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
docker compose up -d --no-deps --wait app
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Check login, tickets, attachment downloads, and consumer access before deleting any old database. Compose constructs the runtime `DATABASE_URL` from `POSTGRES_PASSWORD` and `KANVIL_DATABASE`; the `DATABASE_URL` entry in `.env` is also available for standalone Node operations. Use `COMPOSE_PROJECT_NAME` consistently if you operate multiple Compose instances.
|
|
185
|
+
|
|
186
|
+
## Account recovery
|
|
187
|
+
|
|
188
|
+
A host operator can replace an existing account's local password:
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
docker compose exec app node dist/recover.mjs --email admin@example.com
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The password is entered privately. For local source development, use `pnpm account:recover --email admin@example.com`. The command uses Better Auth's password format and revokes that account's sessions. Its role, linked OIDC account, and API credentials are retained. Revoke agent credentials separately if an account was compromised. Passwords are never accepted as command-line arguments.
|
|
195
|
+
|
|
196
|
+
## Updates and validation
|
|
197
|
+
|
|
198
|
+
Back up first and review changes. For published images, set the desired `KANVIL_IMAGE` in `.env`, then run `docker compose pull app` and `docker compose up -d --no-deps --wait app`. Source builds use `docker compose -f compose.yml -f compose.build.yml up -d --build --wait`. Startup applies the committed schema migrations under a database advisory lock. Database migrations and the application version should be upgraded together. Restore a backup into a new database if an incompatible database change requires rollback.
|
|
199
|
+
|
|
200
|
+
`pnpm test:docker` builds the image and starts an isolated Compose instance with generated test secrets. A real browser creates an owner, project, and ticket; the official MCP client connects; then the test recovers the account, backs up and restores into a new database, and verifies sessions, scoped credentials, tickets, and original attachment bytes. It also checks that existing backup files and databases are refused. Its containers and test volumes are removed afterward.
|
|
201
|
+
|
|
202
|
+
For standalone Node hosting, run `pnpm build`, provide the same configuration through the environment, and run `NODE_ENV=production pnpm --filter @kanvil/server start`. Use a PostgreSQL 18 service you operate and include its database in your backup process. The Docker backup/restore commands target the Compose database service.
|
|
203
|
+
|
|
204
|
+
Project icon rendering uses a bundled offline Lucide collection through Iconify. It does not require an external icon API. Icon license notices are served at `/third-party-icons.txt`; Kanvil remains MIT licensed.
|
package/docs/themes.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Appearance and themes
|
|
2
|
+
|
|
3
|
+
Kanvil's default **Aligned** palette uses neutral light/dark surfaces and an indigo accent. The layout follows Linear's [current interface guidance](https://linear.app/now/behind-the-latest-design-refresh): compact headers, quieter navigation, softer separators, and more information in each row. Kanvil keeps its own branding and shadcn components.
|
|
4
|
+
|
|
5
|
+
Choose Light, Dark, or System under **Settings → Appearance**. Search the theme gallery by name. Choices and custom themes persist in this browser. The default respects the system appearance; switching palettes does not change that preference.
|
|
6
|
+
|
|
7
|
+
The picker follows T3 Code's System/Light/Dark preview tiles and paired color orbs. A light or dark orb assigns a theme only to that appearance; System switches between the two saved choices. Selecting a theme entry applies both appearances and clears the mix. Rings and sun/moon markers indicate the assigned appearances without checkmarks. Theme entries expose duplicate actions, plus edit, export, and delete actions for custom themes. Create theme starts from the current light/dark mix; Add theme imports the existing version-one JSON format. Removing a custom theme resets only appearances that used it. T3 Code's MIT notice for the adapted preview artwork is included in `/third-party-themes.txt`.
|
|
8
|
+
|
|
9
|
+
## Bundled palettes
|
|
10
|
+
|
|
11
|
+
| Theme | Light / dark palette source |
|
|
12
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
13
|
+
| Aligned | Kanvil's adaptation of Linear's neutral interface |
|
|
14
|
+
| Catppuccin Mocha, Macchiato, Frappé | [Catppuccin](https://github.com/catppuccin/palette); Latte in light mode |
|
|
15
|
+
| Solarized | [Ethan Schoonover's Solarized](https://ethanschoonover.com/solarized/) |
|
|
16
|
+
| GitHub | [GitHub Primer](https://github.com/primer/primitives) |
|
|
17
|
+
| Dracula | [Dracula and Alucard](https://github.com/dracula/dracula-theme) |
|
|
18
|
+
| One Dark | [Atom One Dark](https://github.com/atom/one-dark-syntax) and [One Light](https://github.com/atom/one-light-syntax) |
|
|
19
|
+
| Nord | [Nord](https://www.nordtheme.com/docs/colors-and-palettes/) |
|
|
20
|
+
| Tokyo Night, Tokyo Night Storm | [Tokyo Night](https://github.com/folke/tokyonight.nvim); Day in light mode |
|
|
21
|
+
| Gruvbox | [Gruvbox](https://github.com/morhetz/gruvbox) |
|
|
22
|
+
| Rosé Pine, Rosé Pine Moon | [Rosé Pine](https://rosepinetheme.com/palette/); Dawn in light mode |
|
|
23
|
+
| Everforest | [Everforest](https://github.com/sainnhe/everforest) |
|
|
24
|
+
| Monokai | [Monokai](https://github.com/tanvirtin/monokai.nvim); a Kanvil light adaptation |
|
|
25
|
+
| Night Owl | [Night Owl and Light Owl](https://github.com/sdras/night-owl-vscode-theme) |
|
|
26
|
+
| Ayu | [Ayu](https://github.com/ayu-theme/ayu-colors) |
|
|
27
|
+
|
|
28
|
+
These are interface adaptations, not editor syntax themes. Backgrounds and accent families come from the upstream palettes; shades are adjusted where needed for readable buttons and text. Derived surfaces, sidebar text, muted text, and status colors use the shared theme helper. Tests enforce at least 4.5:1 text contrast across the principal surfaces in both modes. Palette notices ship at `/third-party-themes.txt`.
|
|
29
|
+
|
|
30
|
+
Old built-in selections resolve to the corresponding named palette. The former default selections, including `linear`, resolve to Aligned. Existing custom themes keep their IDs and colors, and their version-one JSON import/export format is unchanged.
|
|
31
|
+
|
|
32
|
+
## Density
|
|
33
|
+
|
|
34
|
+
Tickets are grouped into collapsible status sections with counts. Ordering applies within each status. All matching tickets are shown, with no pagination or Load more controls.
|
|
35
|
+
|
|
36
|
+
Ticket lists default to Compact rows. **Display → Row spacing → Comfortable** adds room without hiding titles or labels. **Display → Label display → All labels** shows every full label and scope inline, wrapping as needed. Property visibility, row spacing, and label display are personal browser preferences. Search, filters, and ordering remain shareable through the URL and saved views. Touch controls retain their larger targets on phones.
|
|
37
|
+
|
|
38
|
+
The ticket list uses one view/action toolbar under the location bar. The project name appears once in the location bar, beside its settings cog. Open the magnifying glass for search and the view dropdown for saved views, saving the current filter, deleting a selected saved view, or selecting all displayed tickets. On narrow screens, metadata shares a line instead of repeating property headings. Label/scope previews show two tags on wider layouts and one on narrow layouts; opening a preview reveals every full tag. Ticket titles remain unclipped.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Initial delivery verification
|
|
2
|
+
|
|
3
|
+
Kanvil's implementation is contained in this repository, under the MIT license. It uses its own PostgreSQL storage and has no Kaneo dependency or import requirement. External agent runners adopt its documented interfaces separately.
|
|
4
|
+
|
|
5
|
+
## Delivered behavior
|
|
6
|
+
|
|
7
|
+
- Projects, stable ticket keys, wrapping titles, Markdown requirements, assignment, labels/scopes, parent/child context, blockers, related/duplicate tickets, comments, activity, attachments, subscriptions, saved views, search/filtering, pagination, bulk edits, archiving, and deletion.
|
|
8
|
+
- Shared versioned HTTP/OpenAPI and MCP operations, permission-filtered durable SSE replay, snapshot recovery, optimistic concurrency, idempotent writes, eligibility reasons, exclusive claims, renew/release/recovery, and attributed evidence.
|
|
9
|
+
- React web and Expo native apps with local/OIDC login, explicit account linking, secure native sessions, live updates, cached reading, and local drafts.
|
|
10
|
+
- Agent-aware Inbox preferences and optional session-bound push registration/delivery, with a transactional outbox, retries, receipts, and access checks.
|
|
11
|
+
- Non-root Docker application plus PostgreSQL, private configuration generation, migrations, local account recovery, and tested backup/restore including attachment bytes.
|
|
12
|
+
- Runnable scoped MCP and durable realtime reference consumers, native build profiles, CI, and operator documentation.
|
|
13
|
+
|
|
14
|
+
## Evidence
|
|
15
|
+
|
|
16
|
+
| Area | Verification |
|
|
17
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| Repository checks | `pnpm check`: formatting, linting, TypeScript, and 44 tests across six suites. Tests use isolated databases. |
|
|
19
|
+
| Domain and permissions | Concurrent/stale writes, idempotent retries, competing/expired claims, persistent ownership fencing, dependency cycles, project/credential isolation, attachment bytes, and account recovery/session revocation. |
|
|
20
|
+
| Browser and OIDC | Chromium workspace flows with 55-ticket pagination, wrapping titles, drafts, stale edits, attachments, credential revocation, hierarchy navigation, actual PocketID virtual-passkey linking/login, and interactive MCP OAuth consent/PKCE. |
|
|
21
|
+
| Realtime and consumers | Ordered replay, live changes, abort/reconnect behavior, service restart, persisted projection checkpoints, expired-cursor snapshots, scoped MCP reads, and stream revocation without consuming per-frame API allowance. Both reference CLI entry points were executed against an isolated server. |
|
|
22
|
+
| Self-hosting | Actual Compose image startup, browser owner/project/ticket creation, official MCP connection, bundled password recovery, revoked sessions, private backups, restoration into a new database, retained sessions/credentials, exact binary attachment readback, and refusal to overwrite backups/databases. |
|
|
23
|
+
| iOS | Development and standalone Release simulator builds. Release code-signature verification, embedded bundle, HTTPS/local login, native system-browser PocketID linking and sign-in, complete ticket context, session restoration, and cached reading. Metro was stopped during Release validation. |
|
|
24
|
+
| Android | Standalone arm64 Release APK compiled by the preview workflow, APK signature verified, embedded JavaScript verified, artifact and MIT license retained. Both native Hermes exports and Expo dependency compatibility checks pass. |
|
|
25
|
+
| Push | Isolated-transport tests with real PostgreSQL cover outbox commits, receipt processing, retries, access changes between sends, session revocation, shared-device account changes, omitted-field compatibility, ticket-deletion notices, and explicit unsubscribe cancellation. Native preference changes were read back through HTTP. |
|
|
26
|
+
|
|
27
|
+
The release configuration and Android artifact checks are recorded in [PR #12](https://github.com/MaudeCode/kanvil/pull/12). Implementation was committed in coherent slices and merged after review and green CI.
|
|
28
|
+
|
|
29
|
+
## Boundaries
|
|
30
|
+
|
|
31
|
+
Production deployment and app-store publication have not been performed. Existing real PocketID accounts and signing credentials were not used; identity-provider checks used a disposable instance.
|
|
32
|
+
|
|
33
|
+
No physical-device installation or runtime validation has been performed. Android emulator execution and device passkeys also remain unvalidated. Preview APKs use a generated debug signing key. iOS simulator signing is ad hoc; real distribution needs operator-owned signing configuration.
|
|
34
|
+
|
|
35
|
+
Real APNs/FCM delivery and notification-tap routing remain unvalidated. They require an Expo project and authorized push/signing configuration. Push is optional and disabled by default; ticket storage, login, Inbox, realtime, and MCP work without it.
|
|
36
|
+
|
|
37
|
+
The reference mirror refreshes a full project snapshot when changes arrive. It is an example of consistent recovery, not an execution scheduler. Apps retain explicit local drafts; offline edits are not automatically submitted. Rich-text editing, custom workflow designers, and separate search/message-broker services remain outside the agreed initial scope.
|
|
38
|
+
|
|
39
|
+
## Interface redesign
|
|
40
|
+
|
|
41
|
+
The web and iPhone interfaces share the palette and brand geometry described in [the design direction](design.md). Web fonts are bundled locally with their OFL license. Existing controls and renderers were extended; ticket mutations and authentication contracts retain their shared implementations.
|
|
42
|
+
|
|
43
|
+
- Chromium regression coverage checks light/dark appearance at 1440, 1024, 650, 390, and 320 pixels, including complete long/unbroken titles, viewport and table overflow, selection, expandable filters, persisted/system theme changes, reduced motion, and keyboard navigation into the main content.
|
|
44
|
+
- An independent WebKit run checked local sign-in, both themes at the same five widths, accessible table rows, complete title bounds, and selection.
|
|
45
|
+
- Projects, account settings, project settings, and Inbox were inspected at desktop and phone widths. Both themes, ticket details, wrapping title editors, and empty states were captured. These checks found no browser exceptions or page-level horizontal overflow. Create/update contract and persisted-search regressions also verify that pasted line breaks become single-line title text. Shared text colors meet a 4.5:1 contrast ratio against both canvas and surface colors in each theme.
|
|
46
|
+
- The redesigned standalone iOS Release app was built, signed, and run with an isolated HTTPS backend. Local sign-in, project and ticket navigation, the header project sheet, status picker, Markdown preview, and native edit/save were exercised. A separate HTTP client confirmed the edited title and incremented version. Session restoration after reinstall/relaunch, dark appearance, and large Dynamic Type ticket reading after relaunch were inspected.
|
|
47
|
+
|
|
48
|
+
The existing 44 tests and browser workspace/OAuth workflows pass. CI additionally exercises the disposable PocketID workflow and self-hosting smoke test. Android runtime is outside this redesign; physical-device, push-delivery, and production boundaries above still apply.
|
|
49
|
+
|
|
50
|
+
## Reproduce
|
|
51
|
+
|
|
52
|
+
The approved anvil is applied to web/native brand components, application and launch-screen assets, favicons, touch/install icons, share metadata, and the README. Branding browser coverage checks the actual self-hosted asset URLs, loaded font, manifest icons, and absolute share-image URLs on root and nested pages. The iOS Release build with build number 2 was installed and its home-screen icon and native branding inspected in both themes; launch-screen resources were generated, compiled, and observed during a recorded cold launch of that binary. See [branding](branding.md) for the source and regeneration command.
|
|
53
|
+
|
|
54
|
+
Use [CONTRIBUTING.md](../CONTRIBUTING.md) for checks and disposable fixtures, [self-hosting](self-hosting.md) for Compose/backup/recovery, [native builds](mobile.md) for simulator/preview/signing setup, and [examples](../examples/README.md) for external consumer integration.
|
|
55
|
+
|
|
56
|
+
## Workspaces, identity, icons, and account administration
|
|
57
|
+
|
|
58
|
+
The workspace/account update was verified locally without publishing a branch or deployment:
|
|
59
|
+
|
|
60
|
+
- `pnpm check`: formatting, linting, workspace TypeScript checks, and 76 passing tests. The two optional live-service tests remain conditional on their isolated infrastructure fixtures.
|
|
61
|
+
- `pnpm test:oidc`: 20 browser workflows passed, including disposable PocketID linking/login, interactive MCP consent/PKCE, workspace switching and deep links, inherited/private access, project icon selection/upload/reset, user administration, and role-dependent cache cleanup.
|
|
62
|
+
- `pnpm test:docker`: packaged browser/API/MCP checks and database backup/restore passed, including workspace association, attachment bytes, and uploaded project icon bytes.
|
|
63
|
+
- PostgreSQL tests upgrade populated pre-workspace data without widening private access; cover inherited/explicit roles, guest and scoped-key isolation, revoked membership, retry receipts, concurrent administrator changes, and library/image validation and cleanup protection.
|
|
64
|
+
- Account tests exercise name/email search, filters, safe response fields, last-administrator protection through role/update/ban/delete paths and normalized URLs, password/session changes, public-profile role restrictions, and concurrent role edits.
|
|
65
|
+
- Rendered project/workspace settings, member directories, user lists/detail sheets, icon selection, and API-key project choices were reviewed in light/dark at 1440, 390, and 320 pixels. Fixes included circular clipping, cached profile photos, sidebar label/prefix overlap, long identity wrapping, and narrow-container settings layouts.
|
|
66
|
+
- Native account role parsing and persisted-cache versions share the same administrator-role rule. This update does not claim a new physical-device installation or validation.
|
|
67
|
+
|
|
68
|
+
All integration fixtures use disposable test-owned databases/accounts. Existing project identifiers and ticket prefixes are retained. Local review remains separate from publication approval.
|
|
69
|
+
|
|
70
|
+
## Linear layout and named palettes
|
|
71
|
+
|
|
72
|
+
The web app now uses compact shared controls, navigation, table rows, settings rows, and typography. Compact and Comfortable ticket density are personal browser preferences. Table layout remains available on laptop widths, with the title taking the remaining space; phone layouts retain wrapping titles and larger touch targets. The shadcn inset sidebar, resizing, keyboard access, and contextual actions remain in use.
|
|
73
|
+
|
|
74
|
+
The default palette is Aligned. Eighteen named themes have light/dark mappings and a searchable gallery. Built-in palette checks cover body, sidebar, muted, secondary, popover, primary-button, and status-text contrast. Custom JSON import/export remains version one, and old built-in IDs resolve to named replacements. Sources and notices are listed in [themes](themes.md).
|
|
75
|
+
|
|
76
|
+
Validation for this pass:
|
|
77
|
+
|
|
78
|
+
- `pnpm check` passes formatting, linting, all workspace type checks, and 80 tests. Two optional live-service suites remain skipped.
|
|
79
|
+
- `pnpm test:e2e` passes 24 browser workflows, including all 18 palettes in both appearances, system appearance, theme persistence, custom import/export, searchable theme choices, compact/comfortable density, list properties, selection, relationships, authentication, and the existing ticket/account/settings workflows.
|
|
80
|
+
- Playwright captured the main page families in both modes at desktop and phone widths. Additional project/workspace/member/user/account/icon checks include 320px layouts. Captures are local under `dist/ui-review/linear-pages`, `dist/ui-review/workspaces-users`, and `dist/ui-review/linear-redesign`; they are not tracked.
|
|
81
|
+
- Existing local preview content was checked through the LAN app. Selection caused zero row movement at 1440px, 390px, and 320px; these checks found no page-level horizontal overflow or browser exceptions.
|
|
82
|
+
|
|
83
|
+
This pass validates the web app. It does not claim a new native build, physical-device validation, or production publication.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Workspaces and project access
|
|
2
|
+
|
|
3
|
+
A Kanvil instance hosts accounts and deployment settings. Workspaces group projects and have their own name, URL key, and members. Personal settings belong to the account. Instance administrators can administer every workspace and project; workspace administrators cannot administer the instance or other workspaces.
|
|
4
|
+
|
|
5
|
+
Workspace roles reuse the existing Read only, Can edit, and Admin roles. Workspace-visible projects inherit workspace roles. Private projects require explicit project membership, except that workspace administrators can manage all projects in their workspace. Explicit project roles override inherited roles, except for workspace administrators. A person with only project membership can see that project and its workspace identity without receiving workspace-wide access.
|
|
6
|
+
|
|
7
|
+
Removing workspace membership also removes that person's explicit memberships in the workspace's projects. Keep at least one workspace administrator. Project-scoped API keys remain bounded to their configured projects and cannot administer an entire workspace. Ticket and project prefixes remain unique across the instance, preserving existing links and key-based API lookups.
|
|
8
|
+
|
|
9
|
+
Existing projects are assigned to a default workspace and remain private. Existing project membership is retained; only instance administrators gain workspace administration automatically. Existing API calls that omit a workspace choose an accessible writable workspace; when none exists, project creation creates a personal workspace for the caller. New web-created projects select their workspace explicitly and offer workspace or private access.
|
|
10
|
+
|
|
11
|
+
Workspace operations use the same operation registry, authorization, transaction lock, idempotency receipts, and HTTP/MCP implementations as projects. Realtime visibility continues to derive from accessible projects, so revoked membership stops event delivery. Workspace changes invalidate project consumers through project events; an empty workspace also refreshes in browser queries on focus.
|
|
12
|
+
|
|
13
|
+
Project icons can use a bundled, searchable open-source icon collection or a normalized image upload. Uploaded bytes use the existing blob storage adapter and authenticated project access. Referenced icons must remain excluded from orphan cleanup, and backup/restore retains them with other blob objects.
|
|
14
|
+
|
|
15
|
+
## Verification
|
|
16
|
+
|
|
17
|
+
- Upgrade populated databases without widening private project access or changing ticket identifiers.
|
|
18
|
+
- Test workspace and private-project isolation, inherited versus explicit roles, scoped API keys, removal, last-admin guards, and idempotent writes through the shared domain.
|
|
19
|
+
- Exercise workspace discovery through HTTP and MCP and access revocation through realtime replay and image routes.
|
|
20
|
+
- Test workspace switching, deep links, settings navigation, member search and role changes, icon selection/upload/removal, and account administration in browser workflows.
|
|
21
|
+
- Review light/dark desktop and phone layouts with real content. Keep publication separate from local verification.
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maudecode/kanvil",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Open-source, self-hosted ticketing for agents and people.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.22.0 <25"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"kanvil": "bin/kanvil.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"apps",
|
|
16
|
+
"docs",
|
|
17
|
+
"compose.yml"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/MaudeCode/kanvil.git"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/MaudeCode/kanvil#readme",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@aws-sdk/client-s3": "3.1152.0",
|
|
29
|
+
"@better-auth/api-key": "1.7.8",
|
|
30
|
+
"@better-auth/cimd": "1.7.8",
|
|
31
|
+
"@better-auth/drizzle-adapter": "1.7.8",
|
|
32
|
+
"@better-auth/expo": "1.7.8",
|
|
33
|
+
"@better-auth/mcp": "1.7.8",
|
|
34
|
+
"@hono/node-server": "2.1.5",
|
|
35
|
+
"@hono/zod-openapi": "1.6.3",
|
|
36
|
+
"@iconify-json/lucide": "1.2.144",
|
|
37
|
+
"@inquirer/password": "5.2.3",
|
|
38
|
+
"@modelcontextprotocol/server": "2.3.1",
|
|
39
|
+
"amqplib": "2.2.0",
|
|
40
|
+
"better-auth": "1.7.8",
|
|
41
|
+
"drizzle-orm": "0.45.4",
|
|
42
|
+
"expo-server-sdk": "7.2.0",
|
|
43
|
+
"hono": "4.13.13",
|
|
44
|
+
"pg": "8.23.1",
|
|
45
|
+
"zod": "4.6.7"
|
|
46
|
+
}
|
|
47
|
+
}
|