v0.1.0 — WOPI host, JWT auth, admin panel
What's new in v0.1.0 — the first self-host release
v0.0.x was the "build a real editor end-to-end" arc. v0.1.0 is the first release that earns its self-host story: real persistence, JWT-secured access, an admin panel, OCI-labelled multi-arch Docker image, complex pivot-cache passthrough, and full self-hosting + customization docs.
v1.0.0 is not this release — semver-bound API stability comes later. v0.1.0 means "production-grade release with substantial new capability" without committing to the v1 API freeze yet.
Real persistence — WOPI host integration
New host.Integration interface + four concrete backends selected via CASUAL_STORAGE:
memory(default) — in-process; preserves the v0.0.x "no DB" shapelocal— filesystem underCASUAL_LOCAL_PATHs3— S3-compatible (AWS · MinIO · Cloudflare R2 · Backblaze B2)postgres— singlecasual_workbookstable withbyteapayload; schema auto-created on first connect
Three WOPI endpoints:
GET /wopi/files/:id— CheckFileInfoGET /wopi/files/:id/contents— GetFilePOST /wopi/files/:id/contents— PutFile (withX-WOPI-ItemVersionhonoured as If-Match → 409 on mismatch)
Backwards-compatible: when CASUAL_STORAGE is unset, the in-memory default keeps every v0.0.x deployment working unchanged.
JWT-secured access
When CASUAL_JWT_SECRET is set, every /wopi/files/* request needs a signed JWT. The claim model:
sub— username, email, or stable user idfile_id— the single file this token authorises (URL:idmust match — tokens can't lateral-move)role—admin·editor·commenter·viewerpermissions— per-flag override:read·write·comment·download·share·adminfeatures— feature toggles consumed by the client UIpassword_required— legacyx-room-passwordgate still applies on topdisplay_name— labels presence + cursor markers
Plus POST /api/tokens (admin-gated mint), GET /api/me (self-introspection).
Admin panel
/admin gated by CASUAL_ADMIN_USERNAME + CASUAL_ADMIN_PASSWORD. Seven sections backed by a single JSON config on disk:
- Branding — app name · accent color · logo URL
- Base path — reverse-proxy sub-path mount with normalisation
- Storage — backend dropdown + per-backend creds + test-connection
- Networking — public origin · CORS allowlist · trust proxy · HSTS max-age
- Room limits — max rooms · max file size · room TTL · max users per room
- Auth providers — JWT (live) + OIDC + SAML (stubs for v0.2)
- Webhooks — array of subscriptions with HMAC signing
Login mints a short-lived admin-role JWT for the session. Secrets in the config are redacted on read (***); the panel preserves prior values when the sentinel is sent back unchanged.
Webhook dispatcher
HMAC-SHA256-signed HTTP POSTs to operator-configured URLs. Nine events: room.created · room.dropped · file.uploaded · file.saved · file.deleted · user.joined · user.left · admin.login · admin.login_failed. Signature header X-Casual-Signature: sha256=<hex>. Single retry after 5 s on failure.
Complex pivot cache passthrough (P6.1)
xlsx files authored with pivot tables now round-trip with their cache + table OOXML preserved — Excel re-recognises the file as having pivots after a save through our pipeline. Audit: 46 / 46 → 54 / 54 probes pristine.
Docker labeling + rolling tags
- OCI
org.opencontainers.image.*labels baked into every image - Rolling-tag scheme:
0.1.0→0.1→0→latest. Pin:0.1for patch updates only - Multi-arch amd64 + arm64
- SBOM + provenance attestations in the OCI manifest
Self-hosting + customization docs
Eleven new doc pages on schnsrw.live/docs/sheets/:
- Self-hosting: overview · reverse-proxy recipes (nginx/Caddy/Traefik) · TLS · CORS · scaling · backups
- Customization: overview · auth (JWT claim model + role matrix + token issuance walkthrough) · webhooks (event catalogue + signature verification in Node/Python/Go)
- ENV.md — canonical env-var reference
Mobile lane
The viewer + light-editor surface that landed on main post-v0.0.6:
- Touch-pan driver synthesizes wheel events from
pointermove(Univer 0.24 has no native touch-pan) - Compact chrome at ≤ 720 px / ≤ 480 px
- Sticky bottom action bar for thumb-reachable formatting
- Formula bar input pinned to 16 px font (iOS focus-zoom guard)
Test coverage
Unit tests: 8 → 60. New suites:
- 9 host-integration contract tests (MemoryHost + LocalHost)
- 7 WOPI-route tests via
fastify.inject() - 21 auth tests (role permissions matrix · token issuance · route enforcement · CheckFileInfo response shape · back-compat)
- 15 admin tests (config store · routes · webhook dispatcher with HMAC signature verification)
E2E suite at 357 + the home + mobile + audit specs.
What's not in v0.1.0 (deferred to v0.2 / later)
- OIDC + SAML backend — UI ships in v0.1; v0.2 ships enforcement without breaking on-disk configs
- Multiple admin accounts — v0.1 uses a single env-set credential pair
- Horizontal scale-out — stateless paths work today; collab plane needs sticky-session + cross-replica awareness backplane in v0.2
- AI / LLM features — Univer's command bus is extensible; left as a v0.3+ slot
Try it
docker run -p 3000:3000 schnsrw/casual-sheets:0.1
# → open http://localhost:3000With persistence + admin panel:
services:
app:
image: schnsrw/casual-sheets:0.1
ports: ['3000:3000']
environment:
REDIS_URL: redis://redis:6379
CASUAL_STORAGE: local
CASUAL_LOCAL_PATH: /data/workbooks
CASUAL_ADMIN_USERNAME: admin
CASUAL_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
CASUAL_JWT_SECRET: ${JWT_SECRET}
volumes:
- data:/data
depends_on:
redis: { condition: service_healthy }
redis:
image: redis:7.4-alpine
command: ['redis-server', '--appendonly', 'yes']
volumes:
data:Full docs: schnsrw.live/docs/sheets/self-hosting/