- Add Overview dashboard as the admin entry point
- Merge Branding configuration into System Settings and unify brand name as siteName
- Independent Email settings from Auth configuration
- Rename Billing to Licensing with more professional iconography
- Reorder navigation items by priority: Overview, Users, Storages, Auth, Email, Settings, Licensing
- Streamline feature registry by simplifying categories and descriptions
- Reduce font to 10px with 30% opacity for watermark effect
- Remove default underline, show only on hover
- Hover slightly increases opacity to 50%
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create shared/feature-registry.ts as the single source of truth
- Define 16 features across 6 categories with typed CellValue per plan
- Derive ProFeature union type from as-const registry (type-safe)
- Add 4 Coming Soon Pro features (audit log, webhooks, SSO, analytics)
- Rewrite ComparisonTable to render from registry with category grouping
- Merge 'Up to 3 Teams' + 'Teams Unlimited' into one quota-diff row
- Add i18n keys for all features and categories (en + zh)
- Remove orphaned ProFeatures const from shared/constants.ts
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add qrcode.react dependency for QR code generation
- Display QR code in PairingModal encoding the pairing URL
- Add countdown timer showing time remaining until code expires
- Auto-expire and stop polling when countdown reaches 0
- All 2809 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add license-state.ts helper for reading/writing license state as
system_options key-value pairs instead of a dedicated singleton table
- Rewrite refresh.ts, has-feature.ts, entitlement.ts, licensing-admin.ts,
licensing-refresh-runner.ts to use license-state helpers
- Generate migration 0014 to drop license_binding table
- Update all 10 test files to use setLicenseOptions instead of
db.insert(licenseBinding)
- All 2809 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Move billing.tsx and billing.test.tsx to admin/ route
- Add billing nav item to admin sidebar with CreditCard icon
- Update UpgradeHint CTA link from /settings/billing to /admin/billing
- Remove billing tab from user settings layout
- Add i18n keys for admin.nav.billing (en: Billing, zh: 订阅管理)
- Regenerate TanStack Router route tree
- Apply migration 0013_licensing to production D1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comprehensive end-to-end test covering:
- Live cloud API contract (pairing create/poll, entitlement 401)
- Feature gates: community (unbound) → Pro features blocked
- Feature gates: Pro binding → all 4 features enabled
- Feature gates: expired cert → features revoked
- Feature gates: partial features (subset of Pro)
- Full lifecycle: pair → approve → activate → open_registration → unbind → blocked
- PASETO verification: reject unknown keys, verify PUBLIC_KEYS configured
- System options: 402 without Pro, 201 with Pro for open_registration
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Send refresh token as Authorization Bearer header (not JSON body)
- Read 'certificate' field from refresh response (was 'entitlement')
- Handle both PASETO tokens and legacy JSON in cached certificates
- Verify PASETO tokens in getPlanFromCert() for admin display
- Extract expires_at from snake_case entitlement in pairing poll
- Update all related tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: v2.6 Z11 — prod public key, Docker cron docs, release notes
- Replace DEV placeholder in public-keys.ts with cloud.zpan.space
production Ed25519 key (k4.public.sphdaogcyIh2_6_yZnO4_xQsi2m52HH9j2CPHcKlGGw)
from cloud C5 cross-repo PR
- Add external cron section to docs/deploy/docker.md for the
POST /api/licensing/refresh-cron endpoint (Z6)
- Create docs/v2.6-release-notes.md with what's new, retroactive gate
notice (open_registration, teams_unlimited, team_quotas), upgrade guide
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* test(licensing): decouple verify/entitlement tests from DEV secret key
Tests were hardcoded to the old DEV placeholder key. Now they generate
a fresh throwaway keypair per suite (beforeAll/afterAll), inject the
public key into PUBLIC_KEYS, and restore the original on teardown.
This keeps the tests independent of whichever production key is in
PUBLIC_KEYS, so rotating the key never breaks the test suite.
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
- server/routes/quotas.ts: gate PUT /:orgId with requireFeature('team_quotas') → 402 on Community
- server/services/matter.ts: add teamQuotaEnabled param to incrementUsageIfAllowed and confirmUpload; when false, skip per-team quota check but still track storage usage
- server/services/save-to-drive.ts: thread teamQuotaEnabled through saveShareToDrive → saveFile/saveFolderRecursive
- server/routes/objects.ts: check hasFeature('team_quotas') before confirmUpload
- server/routes/shares.ts: skip isQuotaSufficient pre-check and pass teamQuotaEnabled to saveShareToDriveService when not Pro
- src/routes/_authenticated/admin/users/index.tsx: hide quota column/button behind useEntitlement('team_quotas'); show UpgradeHint when not Pro
- server/test/setup.ts: add seedProLicense helper for integration tests
- Update affected integration tests to seed Pro license where quota enforcement is expected
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
- Add COMMUNITY_TEAM_LIMIT=3 to shared/constants so both server and frontend share one source of truth
- Add server/services/team-count-guard.ts: countUserOrgs + checkTeamLimit (consults licensing state)
- Hook into better-auth organization.beforeCreateOrganization to throw 402 when limit is reached
- Update /teams UI: isAtLimit guard (with loading protection) shows ProBadge on "New Team" button and opens UpgradeHint dialog instead of create dialog when user is at limit
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
* feat: v2.6 Z7 white-label branding — logo, favicon, wordmark, hide footer
Implements the white-label branding feature gated by `white_label` Pro entitlement:
Backend:
- GET /api/branding (public) — returns BrandingConfig from systemOptions
- PUT /api/admin/branding (admin + requireFeature) — multipart upload for
logo/favicon files + wordmark_text/hide_powered_by fields
- DELETE /api/admin/branding/:field (admin + requireFeature) — resets one field
- server/services/branding.ts — S3 upload to _system/branding/, atomic
upsertOption via onConflictDoUpdate
Frontend:
- BrandingProvider wraps the app; fetches branding on boot, applies favicon via
<link>, sets --site-wordmark CSS var
- AppSidebar uses branding context: custom logo src, wordmark text, and
"Powered by ZPan" footer (hidden when hide_powered_by is set)
- Admin /branding page: shows UpgradeHint for non-Pro, upload form with live
preview panel for Pro users
- BrandingConfig and BrandingField types moved to shared/types/ per convention
Tests: integration tests for all auth guards (401/403/402), validation (415/422),
and happy paths; frontend api.test.ts covers getBranding, saveBranding,
resetBrandingField.
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
* test: add S3 upload and validation coverage for branding routes
Add integration tests for logo/favicon file upload, MIME validation,
size limits, missing storage, and seeded branding values to bring
patch coverage above codecov threshold.
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
---------
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
- Add server/services/signup-mode-guard.ts: getEffectiveSignupMode()
applies Pro check when stored mode is 'open'; non-Pro falls back to
invite-only so downgraded instances stay secure
- Update server/auth.ts: replace internal getSignupMode() with
getEffectiveSignupMode() from new service
- Update server/routes/system.ts: PUT auth_signup_mode=open returns 402
feature_not_available when open_registration feature is absent
- Update RegistrationModeSection: 'open' radio disabled with ProBadge
for non-Pro; clicking it opens UpgradeHint dialog instead of saving
- Add integration tests: 6-combination matrix (3 modes × 2 plans) plus
admin API guard tests
- Update auth.integration.test.ts: split open-mode tests for Pro/non-Pro
Agent-Profile: https://agent-kanban.dev/agents/a6bb038c4226a87f
Co-authored-by: Bob <aibob@mails.agent-kanban.dev>
- Add Billing tab to settings route layout
- Create BillingPage with unbound (comparison table + Connect CTA) and bound states
- Create PairingModal with device-code flow: calls POST /api/licensing/pair,
shows code + pairing_url, polls every 5s, auto-closes on approval
- Create ComparisonTable showing Community vs Pro feature list from v2.6 spec
- Create BoundStatusCard showing email, plan, features, expiry, refresh controls,
and disconnect dialog
- Register billing route in routeTree.gen.ts
- Add i18n keys for en and zh locales
- Add billing.test.tsx with pure logic tests for all components
Agent-Profile: https://agent-kanban.dev/agents/b724a773425e397c